org.syncany.plugins.samba.SambaTransferManager.java Source code

Java tutorial

Introduction

Here is the source code for org.syncany.plugins.samba.SambaTransferManager.java

Source

/*
 * Syncany, www.syncany.org
 * Copyright (C) 2011-2014 Philipp C. Heckel <philipp.heckel@gmail.com>
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */
package org.syncany.plugins.samba;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import jcifs.smb.NtlmPasswordAuthentication;
import jcifs.smb.SmbException;
import jcifs.smb.SmbFile;
import jcifs.smb.SmbFileInputStream;
import jcifs.smb.SmbFileOutputStream;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.syncany.config.Config;
import org.syncany.plugins.transfer.AbstractTransferManager;
import org.syncany.plugins.transfer.StorageException;
import org.syncany.plugins.transfer.StorageFileNotFoundException;
import org.syncany.plugins.transfer.StorageMoveException;
import org.syncany.plugins.transfer.TransferManager;
import org.syncany.plugins.transfer.files.ActionRemoteFile;
import org.syncany.plugins.transfer.files.CleanupRemoteFile;
import org.syncany.plugins.transfer.files.DatabaseRemoteFile;
import org.syncany.plugins.transfer.files.MultichunkRemoteFile;
import org.syncany.plugins.transfer.files.RemoteFile;
import org.syncany.plugins.transfer.files.SyncanyRemoteFile;
import org.syncany.plugins.transfer.files.TempRemoteFile;
import org.syncany.plugins.transfer.files.TransactionRemoteFile;
import org.syncany.util.FileUtil;

/**
 * Implements a {@link TransferManager} based on an Samba storage backend for the
 * {@link SambaTransferPlugin}.
 * 
 * <p>Using an {@link SambaTransferSettings}, the transfer manager is configured and uses
 * a well defined Samba share and folder to store the Syncany repository data. While repo and
 * master file are stored in the given folder, databases and multichunks are stored
 * in special sub-folders:
 * 
 * <ul>
 * <li>The <tt>databases</tt> folder keeps all the {@link DatabaseRemoteFile}s</li>
 * <li>The <tt>multichunks</tt> folder keeps the actual data within the {@link MultiChunkRemoteFile}s</li>
 * </ul>
 * 
 * <p>All operations are auto-connected, i.e. a connection is automatically
 * established.
 *
 * @author Christian Roth <christian.roth@port17.de>
 */
public class SambaTransferManager extends AbstractTransferManager {
    private static final Logger logger = Logger.getLogger(SambaTransferManager.class.getSimpleName());

    private NtlmPasswordAuthentication authentication;
    private String repoPath;
    private String multichunksPath;
    private String databasesPath;
    private String actionsPath;
    private String transactionsPath;
    private String tempPath;

    public SambaTransferManager(SambaTransferSettings connection, Config config) {
        super(connection, config);

        this.repoPath = "smb://" + connection.getHostname() + "/" + connection.getShare();
        this.multichunksPath = "/multichunks/";
        this.databasesPath = "/databases/";
        this.actionsPath = "/actions/";
        this.transactionsPath = "/transactions/";
        this.tempPath = "/temporary/";

        this.authentication = new NtlmPasswordAuthentication("", connection.getUsername(),
                connection.getPassword());

        if (logger.isLoggable(Level.INFO)) {
            logger.log(Level.INFO, "Samba: RepoPath is " + repoPath);
        }
    }

    public SambaTransferSettings getSettings() {
        return (SambaTransferSettings) settings;
    }

    @Override
    public void connect() throws StorageException {
        // make a connect
        try {
            new SmbFile(repoPath, authentication).exists();
        } catch (Exception e) {
            throw new StorageException("Unable to connect to target at " + repoPath + "/" + getSettings().getPath(),
                    e);
        }
    }

    @Override
    public void disconnect() {
        // Nothing
    }

    @Override
    public void init(boolean createIfRequired) throws StorageException {
        connect();

        try {
            if (!testTargetExists() && createIfRequired) {
                new SmbFile(repoPath + "/" + getSettings().getPath(), authentication).mkdirs();
            }

            createSmbFile(RemoteFile.createRemoteFile(multichunksPath, SambaRemoteFile.class)).mkdir();
            createSmbFile(RemoteFile.createRemoteFile(databasesPath, SambaRemoteFile.class)).mkdir();
            createSmbFile(RemoteFile.createRemoteFile(actionsPath, SambaRemoteFile.class)).mkdir();
            createSmbFile(RemoteFile.createRemoteFile(transactionsPath, SambaRemoteFile.class)).mkdir();
            createSmbFile(RemoteFile.createRemoteFile(tempPath, SambaRemoteFile.class)).mkdir();
        } catch (MalformedURLException | SmbException e) {
            throw new StorageException("init: Cannot create required directories", e);
        } finally {
            disconnect();
        }
    }

    @Override
    public void download(RemoteFile remoteFile, File localFile) throws StorageException {
        if (remoteFile.getName().equals(".") && !remoteFile.getName().equals("..")) {
            return;
        }

        try {
            // Download file
            File tempFile = createTempFile(localFile.getName());
            OutputStream tempFOS = new FileOutputStream(tempFile);
            SmbFile requestedSmbFile = createSmbFile(remoteFile);

            if (logger.isLoggable(Level.INFO)) {
                logger.log(Level.INFO, "Samba: Downloading {0} to temp file {1}",
                        new Object[] { requestedSmbFile.getPath(), tempFile });
            }

            try {
                SmbFileInputStream smbfis = new SmbFileInputStream(requestedSmbFile);
                IOUtils.copy(smbfis, tempFOS);

                tempFOS.close();
                smbfis.close();
            } catch (IOException e) {
                logger.log(Level.WARNING, "Samba: Downloading FAILED. {0} to temp file {1}",
                        new Object[] { requestedSmbFile.getPath(), tempFile });
                throw new StorageFileNotFoundException("Samba: Downloading FAILED: " + requestedSmbFile.getPath(),
                        e);
            }

            // Move file
            if (logger.isLoggable(Level.INFO)) {
                logger.log(Level.INFO, "Samba: Renaming temp file {0} to file {1}",
                        new Object[] { tempFile, localFile });
            }

            localFile.delete();
            FileUtils.moveFile(tempFile, localFile);
            tempFile.delete();
        } catch (IOException ex) {
            logger.log(Level.SEVERE, "Error while downloading file " + remoteFile.getName(), ex);
            throw new StorageException(ex);
        }
    }

    @Override
    public void upload(File localFile, RemoteFile remoteFile) throws StorageException {
        try {
            // Upload to temp file
            InputStream fileFIS = new FileInputStream(localFile);
            SmbFile tempSmbFile = createSmbFile(
                    RemoteFile.createRemoteFile("/temp-" + remoteFile.getName(), SambaRemoteFile.class));

            if (logger.isLoggable(Level.INFO)) {
                logger.log(Level.INFO, "Samba: Uploading {0} to temp file {1}",
                        new Object[] { localFile, tempSmbFile.getPath() });
            }

            SmbFileOutputStream smbfos = new SmbFileOutputStream(tempSmbFile);
            IOUtils.copy(fileFIS, smbfos);

            fileFIS.close();
            smbfos.close();

            // Move
            SmbFile smbFile = createSmbFile(remoteFile);
            if (logger.isLoggable(Level.INFO)) {
                logger.log(Level.INFO, "Samba: Renaming temp file {0} to {1}",
                        new Object[] { tempSmbFile.getPath(), smbFile.getPath() });
            }

            tempSmbFile.renameTo(smbFile);
        } catch (IOException ex) {
            logger.log(Level.SEVERE, "Could not upload file " + localFile + " to " + remoteFile.getName(), ex);
            throw new StorageException(ex);
        }
    }

    @Override
    public boolean delete(RemoteFile remoteFile) throws StorageException {
        try {
            createSmbFile(remoteFile).delete();
            return true;
        } catch (IOException ex) {
            logger.log(Level.SEVERE, "Could not delete file " + remoteFile.getName(), ex);
            throw new StorageException(ex);
        }
    }

    @Override
    public void move(RemoteFile sourceFile, RemoteFile targetFile) throws StorageException {
        try {
            SmbFile sourceRemoteFile = createSmbFile(sourceFile);
            SmbFile targetRemoteFile = createSmbFile(targetFile);

            sourceRemoteFile.renameTo(targetRemoteFile);
        } catch (SmbException e) {
            logger.log(Level.SEVERE, "Could not rename/move file " + sourceFile + " to " + targetFile, e);
            throw new StorageMoveException("Could not rename/move file " + sourceFile + " to " + targetFile, e);
        } catch (Exception e) {
            logger.log(Level.SEVERE,
                    "Invalid file name for source or target file: " + sourceFile + " to " + targetFile, e);
            throw new StorageException(
                    "Invalid file name for source or target file: " + sourceFile + " to " + targetFile, e);
        }
    }

    @Override
    public <T extends RemoteFile> Map<String, T> list(Class<T> remoteFileClass) throws StorageException {
        try {
            // List folder
            SmbFile remoteSmbFolder = createSmbFile(
                    RemoteFile.createRemoteFile(getRemoteFilePath(remoteFileClass), SambaRemoteFile.class));

            // Create RemoteFile objects
            Map<String, T> remoteFiles = new HashMap<String, T>();

            for (SmbFile entry : remoteSmbFolder.listFiles()) {
                try {
                    T remoteFile = RemoteFile.createRemoteFile(entry.getName(), remoteFileClass);
                    remoteFiles.put(entry.getName(), remoteFile);
                } catch (Exception e) {
                    logger.log(Level.INFO, "Cannot create instance of " + remoteFileClass.getSimpleName()
                            + " for file " + entry.getName() + "; maybe invalid file name pattern. Ignoring file.");
                }
            }
            return remoteFiles;
        } catch (IOException e) {
            logger.log(Level.SEVERE, "Unable to list Samba directory.", e);
            throw new StorageException(e);
        }
    }

    @Override
    public boolean testTargetCanWrite() {
        try {
            if (createSmbFile(null).isDirectory()) {
                SmbFile smbfile = createSmbFile(
                        RemoteFile.createRemoteFile("syncany-write-test", SambaRemoteFile.class));

                SmbFileOutputStream smbFIS = new SmbFileOutputStream(smbfile);
                smbFIS.write("test".getBytes());
                smbFIS.close();

                smbfile.delete();

                logger.log(Level.INFO, "testTargetCanWrite: Can write, test file created/deleted successfully.");
                return true;
            } else {
                logger.log(Level.INFO, "testTargetCanWrite: Can NOT write, target does not exist.");
                return false;
            }
        } catch (Exception e) {
            logger.log(Level.INFO, "testTargetCanWrite: Can NOT write to target.", e);
            return false;
        }
    }

    @Override
    public boolean testTargetExists() {
        try {
            if (createSmbFile(null).isDirectory()) {
                logger.log(Level.INFO, "testTargetExists: Target does exist.");
                return true;
            } else {
                logger.log(Level.INFO, "testTargetExists: Target does NOT exist.");
                return false;
            }
        } catch (Exception e) {
            logger.log(Level.WARNING, "testTargetExists: Target does NOT exist, error occurred.", e);
            return false;
        }
    }

    @Override
    public boolean testTargetCanCreate() {
        // Find parent path
        String repoPathNoSlash = FileUtil.removeTrailingSlash(getSettings().getPath());
        int repoPathLastSlash = repoPathNoSlash.lastIndexOf("/");
        String parentPath = (repoPathLastSlash > 0) ? repoPathNoSlash.substring(0, repoPathLastSlash) : "/";

        // Test parent path permissions
        try {
            SmbFile parentSmbFolder = new SmbFile(
                    URI.create(repoPath + "/" + parentPath + "/").normalize().toString(), authentication);

            if (parentSmbFolder.isDirectory()) {
                SmbFile testSmbFolder = new SmbFile(URI
                        .create(repoPath + "/" + parentPath + "/" + "syncany-folder-test/").normalize().toString(),
                        authentication);
                testSmbFolder.mkdirs();
                testSmbFolder.delete();

                logger.log(Level.INFO, "testTargetCanCreate: Can create target at " + parentPath);
                return true;
            } else {
                logger.log(Level.INFO, "testTargetCanWrite: Can NOT create target at" + parentSmbFolder.getPath());
                return false;
            }
        } catch (Exception e) {
            logger.log(Level.INFO, "testTargetCanWrite: Can NOT create target at " + parentPath + ".", e);
            return false;
        }
    }

    @Override
    public boolean testRepoFileExists() {
        try {
            SmbFile remoteRepoFile = createSmbFile(new SyncanyRemoteFile());

            if (remoteRepoFile.isFile()) {
                logger.log(Level.INFO, "testRepoFileExists: Repo file exists at " + remoteRepoFile);
                return true;
            } else {
                logger.log(Level.INFO, "testRepoFileExists: Repo file DOES NOT exist at " + remoteRepoFile);
                return false;
            }
        } catch (Exception e) {
            logger.log(Level.INFO, "testRepoFileExists: Exception when trying to check repo file existence.", e);
            return false;
        }
    }

    private SmbFile createSmbFile(RemoteFile remoteFile) throws MalformedURLException {
        if (remoteFile != null) {
            return new SmbFile(URI
                    .create(repoPath + "/" + getSettings().getPath() + "/"
                            + getRemoteFilePath(remoteFile.getClass()) + "/" + remoteFile.getName())
                    .normalize().toString(), authentication);
        } else {
            return new SmbFile(URI.create(repoPath + "/" + getSettings().getPath()).toString(), authentication);
        }
    }

    private String getRemoteFilePath(Class<? extends RemoteFile> remoteFile) {
        if (remoteFile.equals(MultichunkRemoteFile.class)) {
            return multichunksPath;
        } else if (remoteFile.equals(DatabaseRemoteFile.class) || remoteFile.equals(CleanupRemoteFile.class)) {
            return databasesPath;
        } else if (remoteFile.equals(ActionRemoteFile.class)) {
            return actionsPath;
        } else if (remoteFile.equals(TransactionRemoteFile.class)) {
            return transactionsPath;
        } else if (remoteFile.equals(TempRemoteFile.class)) {
            return tempPath;
        } else {
            return "";
        }
    }

    protected static class SambaRemoteFile extends RemoteFile {
        public SambaRemoteFile(String name) throws StorageException {
            super(name);
        }
    }
}