/* * Copyright (C) 2010-2015, Martin Goellnitz * * 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 St, Fifth Floor, Boston, MA, 02110-1301, USA */ package jfs.sync.dav; import com.github.sardine.DavResource; import com.github.sardine.Sardine; import com.github.sardine.SardineFactory; import com.github.sardine.impl.SardineException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import jfs.conf.JFSConfig; import jfs.sync.encryption.AbstractMetaStorageAccess; import jfs.sync.encryption.FileInfo; import jfs.sync.encryption.StorageAccess; import jfs.sync.util.DavUtils; import jfs.sync.util.WindowsProxySelector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Storage access with encrypted files, separate meta data file for each directory and a WebDAV backend. */ public class DavStorageAccess extends AbstractMetaStorageAccess implements StorageAccess { private static final Logger LOG = LoggerFactory.getLogger(DavStorageAccess.class); private Sardine sardine = null; /* * To speed things up we have a second DavResource based directory cache */ private final Map<String, List<DavResource>> directoryCache = new HashMap<>(); public DavStorageAccess(String cipher) { super(cipher, false); } // DavStorageAccess() private Sardine getSardine() { if (sardine==null) { String username = JFSConfig.getInstance().getServerUserName(); String passphrase = JFSConfig.getInstance().getServerPassPhrase(); sardine = SardineFactory.begin(username, passphrase, WindowsProxySelector.getInstance()); LOG.debug("getSardine() webdav client {}", sardine); } // if return sardine; } // getSardine() @Override public String getSeparator() { return "/"; } // getSeparator() private String getUrl(String rootPath, String relativePath) { String urlSegment = getFileName(relativePath); try { urlSegment = URLEncoder.encode(urlSegment, "UTF-8").replace("%2F", getSeparator()); } catch (UnsupportedEncodingException e) { LOG.error("getUrl() System doesn't know UTF8 ?!?!"); } // try/catch LOG.debug("getUrl({}) {}{}", relativePath, rootPath, urlSegment); return rootPath+urlSegment; } // getUrl() private List<DavResource> getListing(String rootPath, String url) throws IOException { if (directoryCache.containsKey(url)) { return directoryCache.get(url); } // if boolean available = true; if (url.length()>rootPath.length()) { String[] pathAndName = getPathAndName(url); available = getListing(rootPath, pathAndName[0]).contains(pathAndName[1]); } // if LOG.info("getListing() listing: {} - {}", url, available); List<DavResource> listing = null; try { if (available) { listing = getSardine().list(url+getSeparator()); LOG.info("getListing({}) listing {}", listing.size(), url); } else { listing = Collections.emptyList(); } // if } catch (Exception e) { listing = Collections.emptyList(); LOG.error("getListing()", e); } // try/catch directoryCache.put(url, listing); return listing; } // getListing() /** * create file info for optionally non existing files * * @param file * @param pathAndName * @return */ private FileInfo createFileInfo(String rootPath, String relativePath, String[] pathAndName) { String url = getUrl(rootPath, relativePath); LOG.debug("createFileInfo() url={}", url); FileInfo result = new FileInfo(); result.setExists(false); result.setCanRead(true); result.setCanWrite(true); result.setPath(pathAndName[0]); result.setName(pathAndName[1]); Collection<DavResource> resources = Collections.emptyList(); try { String[] urlPathAndName = getPathAndName(url); LOG.debug("createFileInfo() - {} / {}", urlPathAndName[0], urlPathAndName[1]); resources = getListing(rootPath, urlPathAndName[0]); for (DavResource resource : resources) { if (urlPathAndName[1].equals(resource.getName())) { result.setDirectory(resource.isDirectory()); result.setExists(true); long modificationTime = DavUtils.getModificationDate(resource); result.setModificationDate(modificationTime); result.setSize(resource.isDirectory() ? 0 : resource.getContentLength()); } // if } // for } catch (Exception e) { LOG.error("createFileInfo()", e); } // try/catch LOG.debug("createFileInfo({}/{}) {}", pathAndName[0], pathAndName[1], result); return result; } // createFileInfo() // TODO: very similar to local file case public FileInfo getFileInfo(String rootPath, String relativePath) { String[] pathAndName = getPathAndName(relativePath); FileInfo result = getParentListing(rootPath, pathAndName).get(pathAndName[1]); if (result==null) { result = createFileInfo(rootPath, relativePath, pathAndName); } // if return result; } // getFileInfo() @Override public boolean createDirectory(String rootPath, String relativePath) { LOG.debug("createDirectory() {}", relativePath); String url = getUrl(rootPath, relativePath); try { getSardine().createDirectory(url); } catch (Exception e) { if (e instanceof SardineException) { SardineException se = (SardineException) e; LOG.warn("createDirectory({}) status code: {} {}", url, se.getStatusCode(), se.getResponsePhrase()); } // if LOG.warn("createDirectory()", e); return false; } // try/catch String[] pathAndName = getPathAndName(relativePath); Map<String, FileInfo> listing = getParentListing(rootPath, pathAndName); LOG.debug("createDirectory({}) pre-listing={}", relativePath, listing); FileInfo info = new FileInfo(); info.setCanRead(true); info.setCanWrite(true); info.setPath(pathAndName[0]); info.setName(pathAndName[1]); info.setDirectory(true); info.setExists(true); info.setModificationDate(0); info.setSize(0); listing.put(pathAndName[1], info); LOG.debug("createDirectory() post-listing={}", listing); LOG.info("createDirectory() flushing {}/: {}", pathAndName[0], listing); flushMetaData(rootPath, pathAndName, listing); LOG.debug("createDirectory() flushing empty path {}/{}", pathAndName[0], pathAndName[1]); listing = Collections.emptyMap(); pathAndName[0] += getSeparator(); pathAndName[0] += pathAndName[1]; flushMetaData(rootPath, pathAndName, listing); return true; } // createDirectory() @Override public boolean setLastModified(String rootPath, String relativePath, long modified) { boolean success = false; String[] pathAndName = getPathAndName(relativePath); Map<String, FileInfo> listing = getParentListing(rootPath, pathAndName); FileInfo info = listing.get(pathAndName[1]); try { String url = getUrl(rootPath, relativePath)+(info.isDirectory() ? "/" : ""); success = DavUtils.setLastModified(sardine, url, modified); } catch (Exception e) { LOG.error("setLastModified()", e); } // try/catch // TODO: starting from here it's the same as with local files if (success) { LOG.info("setLastModified() flushing {}/{}", pathAndName[0], pathAndName[1]); info.setModificationDate(modified); flushMetaData(rootPath, pathAndName, listing); } // if return success; } // setLastModified() @Override public boolean setReadOnly(String rootpath, String path) { return false; } // setReadOnly() @Override public boolean delete(String rootPath, String relativePath) { String[] pathAndName = getPathAndName(relativePath); Map<String, FileInfo> listing = getParentListing(rootPath, pathAndName); LOG.debug("delete() {}", relativePath); LOG.debug("delete() listing={}", listing); // remove named item if (listing.containsKey(pathAndName[1])) { FileInfo info = listing.get(pathAndName[1]); listing.remove(pathAndName[1]); LOG.info("delete() flushing {}/{}", pathAndName[0], pathAndName[1]); flushMetaData(rootPath, pathAndName, listing); LOG.info("delete() listing={}", listing); if (info.isDirectory()) { String metaDataPath = getMetaDataPath(relativePath); String metaDataUrl = getUrl(rootPath, metaDataPath); try { getSardine().delete(metaDataUrl); } catch (Exception e) { LOG.warn("delete()", e); return false; } // try/catch } // if try { getSardine().delete(getUrl(rootPath, relativePath)+(info.isDirectory() ? "/" : "")); } catch (Exception e) { LOG.warn("delete()", e); return false; } // try/catch } // if return true; } // delete() @Override public InputStream getInputStream(String rootpath, String path) throws IOException { String url = getUrl(rootpath, path); return getSardine().get(url); } // getInputStream() protected OutputStream getOutputStream(String rootPath, final String relativePath, final boolean forPayload) throws IOException { LOG.debug("getOutputStream() {}", relativePath); final String url = getUrl(rootPath, relativePath); String[] pathAndName = getPathAndName(relativePath); if (forPayload&&(!getSardine().exists(url))) { FileInfo info = createFileInfo(rootPath, relativePath, pathAndName); Map<String, FileInfo> listing = getParentListing(rootPath, pathAndName); listing.put(info.getName(), info); LOG.info("getOutputStream() flushing {}/{}: {}", pathAndName[0], pathAndName[1], listing); flushMetaData(rootPath, pathAndName, listing); LOG.debug("getOutputStream() getting output stream for {} {}", url, info); } // if OutputStream result = new com.gc.iotools.stream.os.OutputStreamToInputStream<String>() { @Override protected String doRead(InputStream input) throws Exception { try { getSardine().put(url, input); } catch (SardineException se) { if ((!forPayload)&&(se.getStatusCode()==403)) { getSardine().put(url, input); } else { throw se; } // if } // try/catch return ""; } }; return result; } // getOutputStream() } // DavStorageAccess