it.anyplace.sync.client.SyncthingClient.java Source code

Java tutorial

Introduction

Here is the source code for it.anyplace.sync.client.SyncthingClient.java

Source

/* 
 * Copyright (C) 2016 Davide Imbriaco
 *
 * This Java file is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package it.anyplace.sync.client;

import it.anyplace.sync.core.configuration.ConfigurationService;
import static com.google.common.base.Objects.equal;
import com.google.common.collect.Lists;
import it.anyplace.sync.core.beans.DeviceAddress;
import it.anyplace.sync.core.beans.FileInfo;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.eventbus.Subscribe;
import com.google.common.base.Supplier;
import javax.annotation.Nullable;
import it.anyplace.sync.core.cache.BlockCache;
import java.util.concurrent.atomic.AtomicBoolean;
import it.anyplace.sync.bep.BlockExchangeConnectionHandler;
import it.anyplace.sync.bep.BlockPuller;
import it.anyplace.sync.bep.BlockPuller.FileDownloadObserver;
import it.anyplace.sync.bep.BlockPusher;
import it.anyplace.sync.bep.BlockPusher.FileUploadObserver;
import it.anyplace.sync.bep.IndexHandler;
import it.anyplace.sync.core.beans.FileBlocks;
import org.apache.commons.lang3.tuple.Pair;
import it.anyplace.sync.core.beans.FolderInfo;
import it.anyplace.sync.repository.repo.SqlRepository;
import it.anyplace.sync.discovery.DiscoveryHandler;
import com.google.common.collect.Sets;
import it.anyplace.sync.discovery.DeviceAddressSupplier;
import java.util.Set;
import it.anyplace.sync.devices.DevicesHandler;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 *
 * @author aleph
 */
public class SyncthingClient implements Closeable {

    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final ConfigurationService configuration;
    private final DiscoveryHandler discoveryHandler;
    private final SqlRepository sqlRepository;
    private final IndexHandler indexHandler;
    private final List<BlockExchangeConnectionHandler> connections = Collections
            .synchronizedList(Lists.<BlockExchangeConnectionHandler>newArrayList());
    private final List<BlockExchangeConnectionHandler> pool = Lists.<BlockExchangeConnectionHandler>newArrayList();
    private final DevicesHandler devicesHandler;

    public SyncthingClient(ConfigurationService configuration) {
        this.configuration = configuration;
        this.sqlRepository = new SqlRepository(configuration);
        indexHandler = new IndexHandler(configuration, sqlRepository, sqlRepository);
        discoveryHandler = new DiscoveryHandler(configuration, sqlRepository);
        devicesHandler = new DevicesHandler(configuration);
        discoveryHandler.getEventBus().register(devicesHandler);
    }

    public void clearCacheAndIndex() {
        logger.info("clear cache");
        indexHandler.clearIndex();
        configuration.edit().setFolders(Collections.<FolderInfo>emptyList()).persistLater();
        BlockCache.getBlockCache(configuration).clear();
    }

    public DevicesHandler getDevicesHandler() {
        return devicesHandler;
    }

    private @Nullable BlockExchangeConnectionHandler borrowFromPool(final DeviceAddress deviceAddress) {
        synchronized (pool) {
            BlockExchangeConnectionHandler connectionHandler = Iterables.find(pool,
                    new Predicate<BlockExchangeConnectionHandler>() {
                        @Override
                        public boolean apply(BlockExchangeConnectionHandler input) {
                            return equal(deviceAddress, input.getAddress());
                        }
                    }, null);
            if (connectionHandler != null) {
                pool.remove(connectionHandler);
                if (connectionHandler.isClosed()) { //TODO check live
                    return borrowFromPool(deviceAddress);
                } else {
                    return connectionHandler;
                }
            } else {
                return null;
            }
        }
    }

    private void returnToPool(BlockExchangeConnectionHandler connectionHandler) {
        synchronized (pool) {
            if (!connectionHandler.isClosed()) {
                pool.add(connectionHandler);
            }
        }
    }

    private BlockExchangeConnectionHandler openConnection(DeviceAddress deviceAddress) throws Exception {
        final BlockExchangeConnectionHandler connectionHandler = new BlockExchangeConnectionHandler(configuration,
                deviceAddress);
        connectionHandler.setIndexHandler(indexHandler);
        connectionHandler.getEventBus().register(indexHandler);
        connectionHandler.getEventBus().register(devicesHandler);
        final AtomicBoolean shouldRestartForNewFolder = new AtomicBoolean(false);
        connectionHandler.getEventBus().register(new Object() {
            @Subscribe
            public void handleConnectionClosedEvent(BlockExchangeConnectionHandler.ConnectionClosedEvent event) {
                connections.remove(connectionHandler);
                synchronized (pool) {
                    pool.remove(connectionHandler);
                }
            }

            @Subscribe
            public void handleNewFolderSharedEvent(BlockExchangeConnectionHandler.NewFolderSharedEvent event) {
                shouldRestartForNewFolder.set(true);
            }
        });
        connectionHandler.connect();
        connections.add(connectionHandler);
        if (shouldRestartForNewFolder.get()) {
            logger.info("restart connection for new folder shared");
            connectionHandler.close();
            return openConnection(deviceAddress);
        } else {
            return connectionHandler;
        }
    }

    public BlockExchangeConnectionHandler getConnection(DeviceAddress deviceAddress) throws Exception {
        BlockExchangeConnectionHandler connectionHandlerFromPool = borrowFromPool(deviceAddress);
        if (connectionHandlerFromPool != null) {
            return connectionHandlerFromPool;
        } else {
            return openConnection(deviceAddress);
        }
    }

    public BlockExchangeConnectionHandler connectToBestPeer() throws Exception {
        try (DeviceAddressSupplier deviceAddressSupplier = discoveryHandler.newDeviceAddressSupplier()) {
            for (DeviceAddress deviceAddress : deviceAddressSupplier) {
                try {
                    logger.debug("connecting to device = {}", deviceAddress);
                    BlockExchangeConnectionHandler connectionHandler = getConnection(deviceAddress);
                    logger.info("aquired connection to device = {}", deviceAddress);
                    return connectionHandler;
                } catch (Exception ex) {
                    logger.warn("error connecting to device = {}", deviceAddress);
                    logger.warn("error connecting to device", ex);
                }
            }
        }
        throw new RuntimeException("unable to aquire connection");
    }

    //    public List<DeviceAddress> getPeerAddressesRanked() {
    //        logger.debug("retrieving all peer addresses");
    //        List<DeviceAddress> list = Lists.newArrayList();
    //        for (String deviceId : configuration.getPeerIds()) {
    //            list.addAll(globalDiscoveryHandler.query(deviceId));
    //        }
    //        Iterables.addAll(list, Iterables.filter(localDiscorveryHandler.waitForAddresses().getDeviceAddresses(), new Predicate<DeviceAddress>() {
    //            @Override
    //            public boolean apply(DeviceAddress a) {
    //                return configuration.getPeerIds().contains(a.getDeviceId());
    //            }
    //        }));
    //        list = AddressRanker.testAndRank(list);
    //        logger.info("peer addresses = \n\n{}\n", AddressRanker.dumpAddressRanking(list));
    //        return list;
    //    }
    /**
     * the supplier throws npe if connection cannot be aquired
     *
     * @return
     */
    public Supplier<BlockExchangeConnectionHandler> getPeerConnectionsSupplier() {
        return new Supplier<BlockExchangeConnectionHandler>() {
            private final Supplier<BlockExchangeConnectionHandler> supplier = getPeerConnectionsSupplierSafe();

            @Override
            public BlockExchangeConnectionHandler get() {
                BlockExchangeConnectionHandler connectionHandler = supplier.get();
                checkNotNull(connectionHandler, "unable to aquire connection");
                return connectionHandler;
            }
        };
    }

    /**
     *
     * the supplier returns null if connection cannot be aquired
     *
     * @return
     */
    public Supplier<BlockExchangeConnectionHandler> getPeerConnectionsSupplierSafe() {
        return new Supplier<BlockExchangeConnectionHandler>() {

            private final DeviceAddressSupplier deviceAddresses = discoveryHandler.newDeviceAddressSupplier(); //TODO close after use
            private final Set<String> deviceIds = Sets.newHashSet();

            @Override
            public BlockExchangeConnectionHandler get() {
                for (final DeviceAddress deviceAddress : deviceAddresses) {
                    if (deviceIds.contains(deviceAddress.getDeviceId())) {
                        continue;
                    }
                    try {
                        BlockExchangeConnectionHandler connection = getConnection(deviceAddress);
                        deviceIds.add(deviceAddress.getDeviceId());
                        return connection;
                    } catch (Exception ex) {
                        logger.warn("error connecting to device = {}", deviceAddress);
                        logger.warn("error connecting to device", ex);
                    }
                }
                return null;
            }
        };
    }

    public IndexHandler waitForRemoteIndexAquired() throws InterruptedException {
        Supplier<BlockExchangeConnectionHandler> supplier = getPeerConnectionsSupplierSafe();
        while (true) {
            try (BlockExchangeConnectionHandler connection = supplier.get()) {
                if (connection == null) {
                    return indexHandler;
                } else {
                    try {
                        indexHandler.waitForRemoteIndexAquired(connection);
                    } catch (Exception ex) {
                        logger.warn("exception while waiting for index", ex);
                    }
                }
            }
        }
    }

    public BlockExchangeConnectionHandler getConnectionForFolder(String folder) {
        Supplier<BlockExchangeConnectionHandler> supplier = getPeerConnectionsSupplier();
        while (true) {
            BlockExchangeConnectionHandler connectionHandler = supplier.get();
            if (connectionHandler.hasFolder(folder)) {
                return connectionHandler;
            } else {
                connectionHandler.close();
            }
        }
    }

    public FileDownloadObserver pullFile(BlockExchangeConnectionHandler connectionHandler, String folder,
            String path) throws InterruptedException {
        Pair<FileInfo, FileBlocks> fileInfoAndBlocks = indexHandler.waitForRemoteIndexAquired(connectionHandler)
                .getFileInfoAndBlocksByPath(folder, path);
        checkNotNull(fileInfoAndBlocks, "file not found in local index for folder = %s path = %s", folder, path);
        return new BlockPuller(configuration, connectionHandler).pullBlocks(fileInfoAndBlocks.getValue());
    }

    public FileDownloadObserver pullFile(String folder, String path) throws InterruptedException {
        BlockExchangeConnectionHandler connectionHandler = getConnectionForFolder(folder);
        Pair<FileInfo, FileBlocks> fileInfoAndBlocks = indexHandler.waitForRemoteIndexAquired(connectionHandler)
                .getFileInfoAndBlocksByPath(folder, path);
        checkNotNull(fileInfoAndBlocks, "file not found in local index for folder = %s path = %s", folder, path);
        return new BlockPuller(configuration, connectionHandler, true).pullBlocks(fileInfoAndBlocks.getValue());
    }

    public FileUploadObserver pushFile(InputStream data, String folder, String path) throws InterruptedException {
        BlockExchangeConnectionHandler connectionHandler = getConnectionForFolder(folder);
        return new BlockPusher(configuration, connectionHandler, true).withIndexHandler(indexHandler).pushFile(data,
                indexHandler.waitForRemoteIndexAquired(connectionHandler).getFileInfoByPath(folder, path), folder,
                path);
    }

    public BlockPusher.IndexEditObserver pushDir(String folder, String path) throws InterruptedException {
        BlockExchangeConnectionHandler connectionHandler = getConnectionForFolder(folder);
        return new BlockPusher(configuration, connectionHandler, true).withIndexHandler(indexHandler)
                .pushDir(folder, path);
    }

    public BlockPusher.IndexEditObserver pushDelete(String folder, String path) throws InterruptedException {
        BlockExchangeConnectionHandler connectionHandler = getConnectionForFolder(folder);
        return new BlockPusher(configuration, connectionHandler, true).withIndexHandler(indexHandler).pushDelete(
                indexHandler.waitForRemoteIndexAquired(connectionHandler).getFileInfoByPath(folder, path), folder,
                path);
    }

    public DiscoveryHandler getDiscoveryHandler() {
        return discoveryHandler;
    }

    public IndexHandler getIndexHandler() {
        return indexHandler;
    }

    @Override
    public void close() {
        devicesHandler.close();
        discoveryHandler.close();
        for (BlockExchangeConnectionHandler connectionHandler : Lists.newArrayList(connections)) {
            connectionHandler.close();
        }
        indexHandler.close();
        sqlRepository.close();
    }

}