package com.openedit.modules.update; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.collections.map.ListOrderedMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.openedit.entermedia.util.SyncFileDownloader; import org.openedit.repository.ContentItem; import com.openedit.OpenEditException; import com.openedit.util.PathUtilities; public class SyncToServer extends SyncFileDownloader { public static final Log log = LogFactory.getLog(SyncToServer.class); public void syncFromServer() { String url = getServerUrl(); //log.info( "downloading from here:" + url ); if( !login() ) { throw new OpenEditException("Could not log in"); } url += getListXml(); String path = " relative path in " + getSyncPath(); log.info("Saving to a " + path); processPath(getSyncPath()); log.info("Completed sync"); } protected void processPath(String inRemotePath) { Element remotelist = listRemoteChanges(inRemotePath); // Make three lists. Remote, local, remote downloads // loop over files look for dates or time changes if (log.isDebugEnabled()) { log.debug("Found these changes on the remote server: " + inRemotePath); } //This should call recursively for each folder syncWithTheseRemoteChanges(remotelist, inRemotePath); } // This bug is only somewhat related: // http://bugs.sun.com/view_bug.do?bug_id=4860999 // // The workaround recommends comparing times in whole seconds. protected void syncWithTheseRemoteChanges(Element remotedata, String inRemotePath) { Map remote = buildRemoteFileMap(remotedata); // Set local Map local = buildLocalFileMap(inRemotePath); //remove local files and folders that we already have equal synchronizeRemoteAndLocalMaps(remote, local); if (remote.size() == 0) { log.info("No files to download for directory " + inRemotePath); } else { log.info("Downloading " + remote.size() + " changed files for: " + inRemotePath); //We downloaded all files and the top levels of any new directories //This will not download recursively try { downloadAllFiles(remote); } catch ( Exception ex ) { throw new OpenEditException(ex); } } //Check for extra files and folders that we have extra deleteExtraFiles(local); //Now check all the local and remote folders // loop over subdirectories calling this method for (Iterator iterator = remotedata.elementIterator(DIR); iterator.hasNext();) { Element dir = (Element) iterator.next(); String next = dir.attributeValue(PATH); processPath(next); } } /* * TODO: Come up with a better method name? This method compares the local * file map to the remote file map based on path and timestamp. Entries * remaining in the remote map represent files that need to be downloaded. * Entries remaining in the local map represent files that can be deleted * locally if a true "sync" is desired. */ protected void synchronizeRemoteAndLocalMaps(Map remote, Map local) { List allremote = new ArrayList(remote.values()); for (Iterator iterator = allremote.iterator(); iterator.hasNext();) { Element remotefile = (Element) iterator.next(); String path = remotefile.attributeValue(PATH); Element localfile = (Element) local.get(path); if (isEquals(remotefile, localfile)) { remote.remove(remotefile.attributeValue(PATH)); // remove it from the lis of files to download if (log.isDebugEnabled()) { if (isDirectory(remotefile)) { log.debug("Local directory exists: " + path); } else { log.debug("File up to date, skipping: " + path); } } } else { if (log.isDebugEnabled()) { log.debug("File has changed: " + path); } } local.remove(path); // If diferent then remote will replace local // anyways } } protected Map buildRemoteFileMap(Element root) { Map remote = ListOrderedMap.decorate(new HashMap()); for (Iterator iterator = root.elementIterator(); iterator.hasNext();) { Element file = (Element) iterator.next(); String path = file.attributeValue(PATH); if (pathPassesExcludes(file.getName(), path)) { remote.put(path, file); } } return remote; } protected void deleteExtraFiles(Map inLocal) throws OpenEditException { for (Iterator iterator = inLocal.values().iterator(); iterator.hasNext();) { Element element = (Element) iterator.next(); File file = getFile(element.attributeValue(PATH)); log.info("deleting " + file.getAbsolutePath()); getFileUtils().deleteAll(file); if (file.exists()) { log.info("was unable to delete file"); getWindowsUtil().delete(file); } } } public Map buildLocalFileMap(String inRemotePath) throws OpenEditException { log.info("Sending file list for " + inRemotePath); Map local = ListOrderedMap.decorate(new HashMap()); Collection children = null; //children = resolveLocalFile( localPath ).listFiles( exclusionFilter ); children = getPageManager().getRepository().getChildrenNames(inRemotePath); if (children != null) { for (Iterator iterator = children.iterator(); iterator.hasNext();) { String path = (String) iterator.next(); ContentItem child = getPageManager().getRepository().getStub(path); String type = child.isFolder() ? DIR : FILE; if (!pathPassesExcludes(type, path)) { continue; } Element e = null; if (child.isFolder()) { e = DocumentHelper.createElement(DIR); } else { e = DocumentHelper.createElement(FILE); e.addAttribute(DATE, "" + child.getLastModified() / 1000L); } e.addAttribute(PATH, path); local.put(path, e); } } log.info("Found " + local.size() + " files for " + inRemotePath); return local; } protected boolean isEquals(Element inRemotefile, Element inLocalfile) { if (inLocalfile == null && inRemotefile == null) { return true; } if (inLocalfile != null) { if (FILE.equalsIgnoreCase(inRemotefile.getName())) { String date = inRemotefile.attributeValue(DATE); String date2 = inLocalfile.attributeValue(DATE); return date.equals(date2); } else if (isDirectory(inRemotefile)) { String remotePath = inRemotefile.attributeValue(PATH); String localPath = inLocalfile.attributeValue(PATH); //Is this needed? Seems like they are equals by def return remotePath.equals(localPath); } } return false; } protected boolean pathPassesExcludes(String inType, String path) { if (DIR.equals(inType) && !path.endsWith("/")) { path = path + "/"; } for (Iterator iterator = getZipUtils().getExcludes().iterator(); iterator.hasNext();) { String excludePattern = (String) iterator.next(); //the path might be a folder if (PathUtilities.match(path, excludePattern)) { return false; } //if the pattern ends with /* } return true; } }