// 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.fs;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import fitnesse.wiki.*;
import fitnesse.wikitext.parser.VariableSource;
import util.FileUtil;
import static java.lang.String.format;
/**
* This is the "old style" page format. content is stored as: WikiPageName/content.txt and WikiPageName/properties.xml.
*
* @see fitnesse.wiki.fs.WikiFilePage
*/
public class FileSystemPage extends BaseWikitextPage implements FileBasedWikiPage {
static final String contentFilename = "content.txt";
static final String propertiesFilename = "properties.xml";
private final File path;
private final transient VersionsController versionsController;
private final transient SubWikiPageFactory subWikiPageFactory;
private final String versionName;
private transient PageData pageData;
public FileSystemPage(final File path, final String name,
final VersionsController versionsController, final SubWikiPageFactory subWikiPageFactory,
final VariableSource variableSource) {
super(name, variableSource);
this.path = path;
this.versionsController = versionsController;
this.subWikiPageFactory = subWikiPageFactory;
this.versionName = null;
}
private FileSystemPage(final File path, final String name, final FileSystemPage parent) {
this(path, name, parent, null, parent.versionsController, parent.subWikiPageFactory, parent.getVariableSource());
}
private FileSystemPage(FileSystemPage page, String versionName) {
this(page.getFileSystemPath(), page.getName(), (page.isRoot() ? null : page.getParent()), versionName,
page.versionsController, page.subWikiPageFactory, page.getVariableSource());
}
protected FileSystemPage(final File path, final String name, final WikiPage parent, final String versionName,
final VersionsController versionsController, final SubWikiPageFactory subWikiPageFactory,
final VariableSource variableSource) {
super(name, parent, variableSource);
this.path = path;
this.versionsController = versionsController;
this.subWikiPageFactory = subWikiPageFactory;
this.versionName = versionName;
}
@Override
public void removeChildPage(final String name) {
final WikiPage childPage = getChildPage(name);
if (childPage != null) {
childPage.remove();
}
}
@Override
public void remove() {
try {
versionsController.delete(getFileSystemPath());
} catch (IOException e) {
throw new WikiPageLoadException(format("Could not remove page %s", new WikiPagePath(this).toString()), e);
}
}
@Override
public WikiPage addChildPage(String pageName) {
WikiPage page = getChildPage(pageName);
if (page == null) {
page = createPage(pageName);
}
return page;
}
private WikiPage createPage(final String pageName) {
if ("true".equalsIgnoreCase(getVariable("wiki.page.old.style"))) {
return new FileSystemPage(new File(getFileSystemPath(), pageName), pageName, this);
} else {
return new WikiFilePage(new File(getFileSystemPath(), pageName + WikiFilePage.FILE_EXTENSION), pageName, this,
null, versionsController, subWikiPageFactory, getVariableSource());
}
}
@Override
public List<WikiPage> getChildren() {
return subWikiPageFactory.getChildren(this);
}
@Override
public WikiPage getChildPage(String childName) {
return subWikiPageFactory.getChildPage(this, childName);
}
@Override
public PageData getData() {
if (pageData == null) {
try {
pageData = getDataVersion();
} catch (IOException e) {
throw new WikiPageLoadException("Could not load page data for page " + path.getPath(), e);
}
}
return new PageData(pageData);
}
public File getFileSystemPath() {
return this.path;
}
@Override
public VersionInfo commit(final PageData data) {
// Note: RecentChanges is not handled by the versionsController?
resetCache();
try {
return versionsController.makeVersion(new ContentFileVersion(data), new PropertiesFileVersion(data));
} catch (IOException e) {
throw new WikiPageLoadException(e);
}
}
@Override
protected void resetCache() {
super.resetCache();
pageData = null;
}
@Override
public Collection<VersionInfo> getVersions() {
return versionsController.history(contentFile(), propertiesFile());
}
private PageData getDataVersion() throws IOException {
FileVersion[] versions = versionsController.getRevisionData(versionName, contentFile(), propertiesFile());
String content = "";
WikiPageProperty properties = null;
try {
for (FileVersion version : versions) {
if (version == null) continue;
if (contentFilename.equals(version.getFile().getName())) {
content = loadContent(version);
} else if (propertiesFilename.equals(version.getFile().getName())) {
properties = loadAttributes(version);
}
}
} catch (IOException e) {
throw new WikiPageLoadException(e);
}
if (properties == null) {
properties = defaultPageProperties();
}
return new PageData(content, properties);
}
@Override
public WikiPage getVersion(String versionName) {
// Just assert the version is valid
try {
versionsController.getRevisionData(versionName, contentFile(), propertiesFile());
} catch (IOException e) {
throw new WikiPageLoadException(format("Could not load version %s for page at %s", versionName, path.getPath()), e);
}
return new FileSystemPage(this, versionName);
}
@Override
public String toString() {
try {
return getClass().getName() + " at " + this.getFileSystemPath() + "#" + (versionName != null ? versionName : "latest");
} catch (final Exception e) {
return super.toString();
}
}
private File contentFile() {
return new File(getFileSystemPath(), contentFilename);
}
private File propertiesFile() {
return new File(getFileSystemPath(), propertiesFilename);
}
private String loadContent(final FileVersion fileVersion) throws IOException {
try (InputStream content = fileVersion.getContent()) {
return FileUtil.toString(content);
}
}
private WikiPageProperties loadAttributes(final FileVersion fileVersion) throws IOException {
final WikiPageProperties props = new WikiPageProperties();
try (InputStream content = fileVersion.getContent()) {
props.loadFromXmlStream(content);
}
props.setLastModificationTime(fileVersion.getLastModificationTime());
return props;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof FileSystemPage)) return false;
FileSystemPage that = (FileSystemPage) other;
return versionName != null ? versionName.equals(that.versionName) : that.versionName == null && super.equals(that);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (versionName != null ? versionName.hashCode() : 0);
return result;
}
class ContentFileVersion implements FileVersion {
final PageData data;
ContentFileVersion(PageData pageData) {
this.data = new PageData(pageData);
}
@Override
public File getFile() {
return contentFile();
}
@Override
public InputStream getContent() throws UnsupportedEncodingException {
String content = data.getContent();
if (content == null) {
return new ByteArrayInputStream("".getBytes());
}
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);
return new ByteArrayInputStream(content.getBytes(FileUtil.CHARENCODING));
}
@Override
public String getAuthor() {
return data.getAttribute(WikiPageProperty.LAST_MODIFYING_USER);
}
@Override
public Date getLastModificationTime() {
return data.getProperties().getLastModificationTime();
}
}
class PropertiesFileVersion implements FileVersion {
final PageData data;
PropertiesFileVersion(PageData pageData) {
this.data = new PageData(pageData);
}
@Override
public File getFile() {
return propertiesFile();
}
@Override
public InputStream getContent() throws IOException {
WikiPageProperties propertiesToSave = new WikiPageProperties(data.getProperties());
removeAlwaysChangingProperties(propertiesToSave);
return new ByteArrayInputStream(propertiesToSave.toXml().getBytes(FileUtil.CHARENCODING));
}
@Override
public String getAuthor() {
return data.getAttribute(WikiPageProperty.LAST_MODIFYING_USER);
}
@Override
public Date getLastModificationTime() {
return data.getProperties().getLastModificationTime();
}
private void removeAlwaysChangingProperties(WikiPageProperties properties) {
properties.remove(WikiPageProperty.LAST_MODIFIED);
}
}
}