/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * For information about the authors of this project Have a look * at the AUTHORS file in the root of this project. */ package net.sourceforge.fullsync.fs.connection; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.HashMap; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import net.sourceforge.fullsync.ConnectionDescription; import net.sourceforge.fullsync.ExceptionHandler; import net.sourceforge.fullsync.fs.File; import net.sourceforge.fullsync.fs.Site; import net.sourceforge.fullsync.fs.buffering.BufferedFile; import org.apache.commons.vfs2.FileObject; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; public class SyncFileBufferedConnection implements BufferedConnection { private static final long serialVersionUID = 2L; class SyncFileDefaultHandler extends DefaultHandler { BufferedConnection bc; AbstractBufferedFile current; SyncFileDefaultHandler(SyncFileBufferedConnection bc) { this.bc = bc; current = (AbstractBufferedFile) bc.getRoot(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { String name = attributes.getValue("Name"); if ("Directory".equals(qName)) { if ("/".equals(name) || ".".equals(name)) { return; } AbstractBufferedFile newDir = new AbstractBufferedFile(bc, name, current, true, true); current.addChild(newDir); current = newDir; } else if ("File".equals(qName)) { AbstractBufferedFile newFile = new AbstractBufferedFile(bc, name, current, false, true); newFile.setSize(Long.parseLong(attributes.getValue("BufferedLength"))); newFile.setLastModified(Long.parseLong(attributes.getValue("BufferedLastModified"))); newFile.setFsSize(Long.parseLong(attributes.getValue("FileSystemLength"))); newFile.setFsLastModified(Long.parseLong(attributes.getValue("FileSystemLastModified"))); current.addChild(newFile); } super.startElement(uri, localName, qName, attributes); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if ("Directory".equals(qName)) { /* * Source Buffer needs to load fs files after buffer info / * Collection fsChildren = current.getUnbuffered().getChildren(); * for( Iterator i = fsChildren.iterator(); i.hasNext(); ) * { * File f = (File)i.next(); * if( current.getChild( f.getName() ) == null ) * current.addChild( new AbstractBufferedFile( bc, f, current, f.isDirectory(), false ) ); * } * /* */ current = (AbstractBufferedFile) current.getParent(); } super.endElement(uri, localName, qName); } } private Site fs; private BufferedFile root; private boolean monitoringFileSystem; public SyncFileBufferedConnection(final Site fs) throws IOException { this.fs = fs; this.monitoringFileSystem = false; loadFromBuffer(); } @Override public boolean isAvailable() { return fs.isAvailable(); } @Override public File createChild(File dir, String name, boolean directory) throws IOException { File n = dir.getUnbuffered().getChild(name); if (n == null) { n = dir.getUnbuffered().createChild(name, directory); } BufferedFile bf = new AbstractBufferedFile(this, n, dir, directory, false); return bf; } @Override public boolean delete(File node) throws IOException { node.getUnbuffered().delete(); ((BufferedFile) node.getParent()).removeChild(node.getName()); return true; } @Override public HashMap<String, File> getChildren(File dir) { return null; } @Override public File getRoot() { return root; } @Override public boolean makeDirectory(File dir) { return false; } @Override public InputStream readFile(File file) { return null; } @Override public OutputStream writeFile(File file) { return null; } @Override public boolean writeFileAttributes(File file) { return false; } protected void updateFromFileSystem(BufferedFile buffered) throws IOException { // load fs entries if wanted Collection<File> fsChildren = buffered.getUnbuffered().getChildren(); for (File uf : fsChildren) { BufferedFile bf = (BufferedFile) buffered.getChild(uf.getName()); if (bf == null) { bf = new AbstractBufferedFile(this, uf, root, uf.isDirectory(), false); buffered.addChild(bf); } if (bf.isDirectory()) { updateFromFileSystem(bf); } } } protected void loadFromBuffer() throws IOException { File fsRoot = fs.getRoot(); File f = fsRoot.getChild(".syncfiles"); root = new AbstractBufferedFile(this, fsRoot, null, true, true); if ((f == null) || !f.exists() || f.isDirectory()) { if (isMonitoringFileSystem()) { updateFromFileSystem(root); } return; } try (InputStream in = new GZIPInputStream(f.getInputStream())) { ByteArrayOutputStream out = new ByteArrayOutputStream((int) f.getSize()); int i; byte[] block = new byte[4096]; while ((i = in.read(block)) > 0) { out.write(block, 0, i); } in.close(); SAXParser sax = SAXParserFactory.newInstance().newSAXParser(); sax.parse(new ByteArrayInputStream(out.toByteArray()), new SyncFileDefaultHandler(this)); } catch (SAXParseException spe) { StringBuilder sb = new StringBuilder(spe.toString()); sb.append("\n Line number: " + spe.getLineNumber()); sb.append("\n Column number: " + spe.getColumnNumber()); sb.append("\n Public ID: " + spe.getPublicId()); sb.append("\n System ID: " + spe.getSystemId() + "\n"); System.err.println(sb.toString()); } catch (IOException | SAXException | ParserConfigurationException | FactoryConfigurationError e) { ExceptionHandler.reportException(e); } if (isMonitoringFileSystem()) { updateFromFileSystem(root); } } protected Element serializeFile(BufferedFile file, Document doc) throws IOException { Element elem = doc.createElement(file.isDirectory() ? "Directory" : "File"); elem.setAttribute("Name", file.getName()); if (file.isDirectory()) { for (File n : file.getChildren()) { if (!n.exists()) { continue; } elem.appendChild(serializeFile((BufferedFile) n, doc)); } } else { elem.setAttribute("BufferedLength", String.valueOf(file.getSize())); elem.setAttribute("BufferedLastModified", String.valueOf(file.getLastModified())); elem.setAttribute("FileSystemLength", String.valueOf(file.getFsSize())); elem.setAttribute("FileSystemLastModified", String.valueOf(file.getFsLastModified())); } return elem; } public void saveToBuffer() throws IOException { File fsRoot = fs.getRoot(); File node = fsRoot.getChild(".syncfiles"); if ((node == null) || !node.exists()) { node = root.createChild(".syncfiles", false); } try { DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = docBuilder.newDocument(); Element e = doc.createElement("SyncFiles"); e.appendChild(serializeFile(root, doc)); doc.appendChild(e); TransformerFactory fac = TransformerFactory.newInstance(); fac.setAttribute("indent-number", 2); Transformer tf = fac.newTransformer(); tf.setOutputProperty(OutputKeys.METHOD, "xml"); tf.setOutputProperty(OutputKeys.VERSION, "1.0"); tf.setOutputProperty(OutputKeys.INDENT, "yes"); tf.setOutputProperty(OutputKeys.STANDALONE, "no"); DOMSource source = new DOMSource(doc); try (OutputStreamWriter osw = new OutputStreamWriter(new GZIPOutputStream(node.getOutputStream()), StandardCharsets.UTF_8)) { tf.transform(source, new StreamResult(osw)); osw.flush(); } } catch (IOException | ParserConfigurationException | FactoryConfigurationError | TransformerException e) { ExceptionHandler.reportException(e); } } @Override public boolean isMonitoringFileSystem() { return monitoringFileSystem; } @Override public void flush() throws IOException { saveToBuffer(); fs.flush(); } @Override public void close() throws IOException { fs.close(); } @Override public boolean isCaseSensitive() { return fs.isCaseSensitive(); } @Override public FileObject getBase() { return fs.getBase(); } @Override public ConnectionDescription getConnectionDescription() { return fs.getConnectionDescription(); } }