// Copyright (C) 2003-2009 by Object Mentor, Inc. All rights reserved.
// Released under the terms of the CPL Common Public License version 1.0.
package fitnesse.wiki;
import fitnesse.ComponentFactory;
import fitnesse.wiki.zip.ZipFileVersionsController;
import fitnesse.wikitext.parser.WikiWordPath;
import util.Clock;
import util.DiskFileSystem;
import util.FileSystem;
import util.FileUtil;
import java.io.*;
import java.lang.reflect.Method;
import java.util.Date;
public class FileSystemPage extends CachingPage {
private static final long serialVersionUID = 1L;
public static final String contentFilename = "/content.txt";
public static final String propertiesFilename = "/properties.xml";
private final String path;
private final VersionsController versionsController;
private CmSystem cmSystem = new CmSystem();
public FileSystemPage(final String path, final String name, final FileSystem fileSystem, final ComponentFactory componentFactory) {
super(name, null);
this.path = path;
versionsController = createVersionsController(componentFactory);
createDirectoryIfNewPage(fileSystem);
}
public FileSystemPage(final String path, final String name) {
this(path, name, new DiskFileSystem(), new ComponentFactory());
}
public FileSystemPage(final String name, final FileSystemPage parent, final FileSystem fileSystem) {
super(name, parent);
path = parent.getFileSystemPath();
versionsController = parent.versionsController;
createDirectoryIfNewPage(fileSystem);
}
private VersionsController createVersionsController(ComponentFactory factory) {
return (VersionsController) factory.createComponent(ComponentFactory.VERSIONS_CONTROLLER,
ZipFileVersionsController.class);
}
@Override
public void removeChildPage(final String name) {
super.removeChildPage(name);
String pathToDelete = getFileSystemPath() + "/" + name;
final File fileToBeDeleted = new File(pathToDelete);
cmSystem.preDelete(pathToDelete);
FileUtil.deleteFileSystemDirectory(fileToBeDeleted);
cmSystem.delete(pathToDelete);
}
@Override
public boolean hasChildPage(final String pageName) {
final File f = new File(getFileSystemPath() + "/" + pageName);
if (f.exists()) {
addChildPage(pageName);
return true;
}
return false;
}
protected synchronized void saveContent(String content) {
if (content == null) {
return;
}
final String separator = System.getProperty("line.separator");
if (content.endsWith("|")) {
content += separator;
}
//First replace every windows style to unix
content = content.replaceAll("\r\n", "\n");
//Then do the replace to match the OS. This works around
//a strange behavior on windows.
content = content.replaceAll("\n", separator);
String contentPath = getFileSystemPath() + contentFilename;
final File output = new File(contentPath);
OutputStreamWriter writer = null;
try {
if (output.exists())
cmSystem.edit(contentPath);
writer = new OutputStreamWriter(new FileOutputStream(output), "UTF-8");
writer.write(content);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Can not decode file " + output, e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (writer != null) {
FileUtil.close(writer);
cmSystem.update(contentPath);
}
}
}
protected synchronized void saveAttributes(final WikiPageProperties attributes)
{
OutputStream output = null;
String propertiesFilePath = "<unknown>";
try {
propertiesFilePath = getFileSystemPath() + propertiesFilename;
File propertiesFile = new File(propertiesFilePath);
if (propertiesFile.exists())
cmSystem.edit(propertiesFilePath);
output = new FileOutputStream(propertiesFile);
WikiPageProperties propertiesToSave = new WikiPageProperties(attributes);
removeAlwaysChangingProperties(propertiesToSave);
propertiesToSave.save(output);
} catch (final Exception e) {
throw new RuntimeException("Failed to save properties file: \""
+ propertiesFilePath + "\" (exception: " + e + ").", e);
} finally {
if (output != null) {
FileUtil.close(output);
cmSystem.update(propertiesFilePath);
}
}
}
private void removeAlwaysChangingProperties(WikiPageProperties properties) {
properties.remove(PageData.PropertyLAST_MODIFIED);
}
@Override
protected WikiPage createChildPage(final String name) {
//return new FileSystemPage(getFileSystemPath(), name, this, this.versionsController);
return new PageRepository().makeChildPage(name, this);
}
private void loadContent(final PageData data) {
String content = "";
final String name = getFileSystemPath() + contentFilename;
final File input = new File(name);
try {
if (input.exists()) {
final byte[] bytes = readContentBytes(input);
content = new String(bytes, "UTF-8");
}
data.setContent(content);
} catch (IOException e) {
throw new RuntimeException("Error while loading content", e);
}
}
@Override
protected void loadChildren() {
final File thisDir = new File(getFileSystemPath());
if (thisDir.exists()) {
final String[] subFiles = thisDir.list();
for (final String subFile : subFiles) {
if (fileIsValid(subFile, thisDir) && !this.children.containsKey(subFile)) {
this.children.put(subFile, getChildPage(subFile));
}
}
}
}
private byte[] readContentBytes(final File input) throws IOException {
FileInputStream inputStream = null;
try {
final byte[] bytes = new byte[(int) input.length()];
inputStream = new FileInputStream(input);
inputStream.read(bytes);
return bytes;
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
private boolean fileIsValid(final String filename, final File dir) {
if (WikiWordPath.isWikiWord(filename)) {
final File f = new File(dir, filename);
if (f.isDirectory()) {
return true;
}
}
return false;
}
private String getParentFileSystemPath() {
return this.parent != null ? ((FileSystemPage) this.parent).getFileSystemPath() : this.path;
}
public String getFileSystemPath() {
return getParentFileSystemPath() + "/" + getName();
}
public String getAbsoluteFileSystemPath() {
return new File(getFileSystemPath()).getAbsolutePath();
}
private void loadAttributes(final PageData data) {
final File file = new File(getFileSystemPath() + propertiesFilename);
if (file.exists()) {
try {
long lastModifiedTime = getLastModifiedTime();
attemptToReadPropertiesFile(file, data, lastModifiedTime);
} catch (final Exception e) {
System.err.println("Could not read properties file:" + file.getPath());
e.printStackTrace();
}
}
}
private long getLastModifiedTime() {
long lastModifiedTime = 0;
final File file = new File(getFileSystemPath() + contentFilename);
if (file.exists()) {
lastModifiedTime = file.lastModified();
} else {
lastModifiedTime = Clock.currentTimeInMillis();
}
return lastModifiedTime;
}
private void attemptToReadPropertiesFile(File file, PageData data,
long lastModifiedTime) throws FileNotFoundException {
InputStream input = null;
try {
final WikiPageProperties props = new WikiPageProperties();
input = new FileInputStream(file);
props.loadFromXmlStream(input);
props.setLastModificationTime(new Date(lastModifiedTime));
data.setProperties(props);
} finally {
if (input != null)
FileUtil.close(input);
}
}
@Override
public void doCommit(final PageData data) {
saveContent(data.getContent());
saveAttributes(data.getProperties());
this.versionsController.prune(this);
}
@Override
protected PageData makePageData() {
final PageData pagedata = new PageData(this);
loadContent(pagedata);
loadAttributes(pagedata);
pagedata.addVersions(this.versionsController.history(this));
return pagedata;
}
public PageData getDataVersion(final String versionName) {
return this.versionsController.getRevisionData(this, versionName);
}
private void createDirectoryIfNewPage(FileSystem fileSystem) {
String pagePath = getFileSystemPath();
if (!fileSystem.exists(pagePath)) {
try {
fileSystem.makeDirectory(pagePath);
} catch (IOException e) {
throw new RuntimeException("Unable to create directory for new page", e);
}
cmSystem.update(pagePath);
}
}
@Override
protected VersionInfo makeVersion() {
final PageData data = getData();
return makeVersion(data);
}
protected VersionInfo makeVersion(final PageData data) {
return this.versionsController.makeVersion(this, data);
}
protected void removeVersion(final String versionName) {
this.versionsController.removeVersion(this, versionName);
}
@Override
public String toString() {
try {
return getClass().getName() + " at " + this.getFileSystemPath();
} catch (final Exception e) {
return super.toString();
}
}
class CmSystem {
public void update(String fileName) {
invokeCmMethod("cmUpdate", fileName);
}
public void edit(String fileName) {
invokeCmMethod("cmEdit", fileName);
}
public void delete(String fileToBeDeleted) {
invokeCmMethod("cmDelete", fileToBeDeleted);
}
public void preDelete(String fileToBeDeleted) {
invokeCmMethod("cmPreDelete", fileToBeDeleted);
}
private void invokeCmMethod(String method, String newPagePath) {
if (getCmSystemClassName() != null) {
try {
Class<?> cmSystem = Class.forName(getCmSystemClassName());
Method updateMethod = cmSystem.getMethod(method, String.class, String.class);
updateMethod.invoke(null, newPagePath, getCmSystemVariable());
} catch (Exception e) {
System.err.println("Could not invoke static " + method + "(path,payload) of " + getCmSystemClassName());
e.printStackTrace();
}
}
}
private String getCmSystemClassName() {
String cmSystemVariable = getCmSystemVariable();
if (cmSystemVariable == null)
return null;
String cmSystemClassName = cmSystemVariable.split(" ")[0].trim();
if (cmSystemClassName == null || cmSystemClassName.equals(""))
return null;
return cmSystemClassName;
}
private String getCmSystemVariable() {
return readOnlyData().getVariable("CM_SYSTEM");
}
}
}