├── .gitignore ├── LICENSE ├── README.md ├── TODO.txt ├── a-sync-bep ├── pom.xml └── src │ └── main │ ├── java │ └── it │ │ └── anyplace │ │ └── sync │ │ └── bep │ │ ├── BlockExchangeConnectionHandler.java │ │ ├── BlockPuller.java │ │ ├── BlockPusher.java │ │ ├── FolderBrowser.java │ │ ├── IndexBrowser.java │ │ ├── IndexFinder.java │ │ ├── IndexHandler.java │ │ ├── beans │ │ └── ClusterConfigFolderInfo.java │ │ └── protos │ │ └── BlockExchageProtos.java │ └── resources │ ├── blockExchageProtos.proto │ └── logback.xml ├── a-sync-client ├── pom.xml └── src │ └── main │ └── java │ └── it │ └── anyplace │ └── sync │ ├── client │ ├── Main.java │ └── SyncthingClient.java │ └── devices │ └── DevicesHandler.java ├── a-sync-core ├── pom.xml └── src │ └── main │ ├── java │ └── it │ │ └── anyplace │ │ └── sync │ │ └── core │ │ ├── beans │ │ ├── BlockInfo.java │ │ ├── DeviceAddress.java │ │ ├── DeviceInfo.java │ │ ├── DeviceStats.java │ │ ├── FileBlocks.java │ │ ├── FileInfo.java │ │ ├── FolderInfo.java │ │ ├── FolderStats.java │ │ └── IndexInfo.java │ │ ├── cache │ │ ├── BlockCache.java │ │ └── FileBlockCache.java │ │ ├── configuration │ │ ├── ConfigurationService.java │ │ └── gsonbeans │ │ │ ├── DeviceConfig.java │ │ │ ├── DeviceConfigList.java │ │ │ ├── FolderConfig.java │ │ │ └── FolderConfigList.java │ │ ├── events │ │ ├── DeviceAddressActiveEvent.java │ │ └── DeviceAddressReceivedEvent.java │ │ ├── interfaces │ │ ├── DeviceAddressRepository.java │ │ ├── IndexRepository.java │ │ ├── RelayConnection.java │ │ ├── Sequencer.java │ │ └── TempRepository.java │ │ ├── logs │ │ ├── LogAppender.java │ │ └── LogService.java │ │ ├── security │ │ └── KeystoreHandler.java │ │ └── utils │ │ ├── BlockUtils.java │ │ ├── ExecutorUtils.java │ │ ├── FileInfoOrdering.java │ │ ├── FileUtils.java │ │ └── PathUtils.java │ └── resources │ └── default.properties ├── a-sync-discovery ├── pom.xml └── src │ └── main │ ├── java │ └── it │ │ └── anyplace │ │ └── sync │ │ └── discovery │ │ ├── DeviceAddressSupplier.java │ │ ├── DiscoveryHandler.java │ │ ├── protocol │ │ ├── gd │ │ │ └── GlobalDiscoveryHandler.java │ │ └── ld │ │ │ ├── LocalDiscorveryHandler.java │ │ │ └── protos │ │ │ └── LocalDiscoveryProtos.java │ │ └── utils │ │ └── AddressRanker.java │ └── resources │ └── localDiscoveryProtos.proto ├── a-sync-http-relay-server ├── deploy.sh ├── pom.xml └── src │ └── main │ ├── java │ └── it │ │ └── anyplace │ │ └── sync │ │ └── relay │ │ └── server │ │ └── Main.java │ └── resources │ └── logback.xml ├── a-sync-http-relay ├── pom.xml └── src │ └── main │ ├── java │ └── it │ │ └── anyplace │ │ └── sync │ │ └── httprelay │ │ ├── client │ │ ├── HttpRelayClient.java │ │ └── HttpRelayConnection.java │ │ ├── protos │ │ └── HttpRelayProtos.java │ │ └── server │ │ ├── HttpRelayServer.java │ │ └── RelaySessionConnection.java │ └── resources │ └── httpRelayProtos.proto ├── a-sync-parent └── pom.xml ├── a-sync-relay ├── pom.xml └── src │ └── main │ └── java │ └── it │ └── anyplace │ └── sync │ └── client │ └── protocol │ └── rp │ ├── RelayClient.java │ └── beans │ └── SessionInvitation.java ├── a-sync-repository ├── pom.xml └── src │ └── main │ ├── java │ └── it │ │ └── anyplace │ │ └── sync │ │ └── repository │ │ └── repo │ │ ├── SqlRepository.java │ │ └── protos │ │ └── IndexSerializationProtos.java │ └── resources │ └── indexSerializationProtos.proto ├── a-sync-webclient ├── nbactions.xml ├── pom.xml └── src │ └── main │ ├── java │ └── it │ │ └── anyplace │ │ └── sync │ │ └── webclient │ │ ├── HttpService.java │ │ └── Main.java │ └── resources │ └── web │ ├── lib │ ├── font-awesome.css │ ├── handlebars.js │ ├── images │ │ ├── ui-icons_444444_256x240.png │ │ ├── ui-icons_555555_256x240.png │ │ ├── ui-icons_777620_256x240.png │ │ ├── ui-icons_777777_256x240.png │ │ ├── ui-icons_cc0000_256x240.png │ │ └── ui-icons_ffffff_256x240.png │ ├── jquery-ui.css │ ├── jquery-ui.js │ ├── jquery.js │ └── less.js │ ├── webclient.html │ ├── webclient.js │ └── webclient.less ├── docs ├── http_relay_protocol.graphml └── http_relay_protocol.png └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /a-sync-core/target/ 2 | /a-sync-relay/target/ 3 | /a-sync-http-relay/target/ 4 | /a-sync-bep/target/ 5 | /a-sync-repository/target/ 6 | /a-sync-discovery/target/ 7 | /a-sync-client/target/ 8 | /a-sync-http-relay-server/target/ 9 | /a-sync-parent/target/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # a-sync 2 | 3 | [![MPLv2 License](https://img.shields.io/badge/license-MPLv2-blue.svg?style=flat-square)](https://www.mozilla.org/MPL/2.0/) 4 | 5 | This project is an java client implementation of [Syncthing][1] protocols (bep, discovery, relay). A-sync is made of several modules, providing: 6 | 7 | 1. a command line utility for accessing a Syncthing network (ie a network of devices that speak Syncthing protocols)' 8 | 9 | 2. a service implementation for the 'http-relay' protocol (that proxies 'relay' protocol over an http connection); 10 | 11 | 3. a client library for Syncthing protocol, that can be used by third-party applications (such as mobile apps) to integrate with Syncthing. 12 | 13 | Care is taken to make sure the client library is compatible with android (min sdk 19), so it can be used for the [a-sync-browser][2] project. 14 | 15 | NOTE: this is a client-oriented implementation, designed to work online by downloading and uploading files from an active device on the network (instead of synchronizing a local copy of the entire repository). This is quite different from the way the original Syncthing app works, and its useful from those implementations that cannot or wish not to download the entire repository (for example, mobile devices with limited storage available, wishing to access a syncthing share). 16 | 17 | DISCLAMER: I'm not the owner fo Syncthing. The Syncthing name, logo and reference documentation is property of the Syncthing team, and licensed under the MPLv2 License. This project is not affiliated with Syncthing, and uses its public avaliable documentation and protocol specification as a reference to implement protocol compatibility. 18 | 19 | Documentation is non-existing as know, if you have any question feel free to contact the developer (me). 20 | 21 | All code is licensed under the [MPLv2 License][3]. 22 | 23 | If you like this work, please donate something to help its development! 24 | 25 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FVB93HNCH5NL8) 26 | 27 | [1]: https://syncthing.net/ 28 | [2]: https://github.com/davide-imbriaco/a-sync-browser 29 | [3]: https://github.com/davide-imbriaco/a-sync/blob/master/LICENSE 30 | 31 | 32 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | 2 | - read peer addresses from cluster config 3 | - update local index on push 4 | - delta index 5 | - index store version/discard unsupported versions 6 | - rename all to anyplace-sync-client 7 | 8 | - retry operation if first BEP connection fail/close 9 | - multi folder mode (change folder at runtime, multi-folder index handler) 10 | 11 | - use absolute path syntax "/path" in db/app 12 | - canonicalize all path on db/app (uri/url?) 13 | 14 | - block cache cleanup, block cache shutdown 15 | - connection wrapper, return to pool on close 16 | 17 | - config file lock (pid) 18 | -------------------------------------------------------------------------------- /a-sync-bep/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | it.anyplace.sync 6 | a-sync-parent 7 | 1.3 8 | ../a-sync-parent 9 | 10 | a-sync-bep 11 | jar 12 | 13 | 14 | 15 | ${project.groupId} 16 | a-sync-core 17 | ${project.version} 18 | 19 | 20 | ${project.groupId} 21 | a-sync-relay 22 | ${project.version} 23 | 24 | 25 | ${project.groupId} 26 | a-sync-http-relay 27 | ${project.version} 28 | 29 | 30 | -------------------------------------------------------------------------------- /a-sync-bep/src/main/java/it/anyplace/sync/bep/BlockPuller.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.bep; 15 | 16 | import com.google.common.base.Function; 17 | import com.google.common.base.Functions; 18 | import static com.google.common.base.Objects.equal; 19 | import com.google.common.collect.Lists; 20 | import com.google.common.collect.Maps; 21 | import com.google.common.collect.Sets; 22 | import com.google.common.eventbus.Subscribe; 23 | import com.google.protobuf.ByteString; 24 | import it.anyplace.sync.core.configuration.ConfigurationService; 25 | import it.anyplace.sync.core.beans.BlockInfo; 26 | import it.anyplace.sync.bep.protos.BlockExchageProtos.ErrorCode; 27 | import it.anyplace.sync.bep.protos.BlockExchageProtos.Request; 28 | import it.anyplace.sync.bep.BlockExchangeConnectionHandler.ResponseMessageReceivedEvent; 29 | import java.io.ByteArrayInputStream; 30 | import java.io.InputStream; 31 | import java.io.SequenceInputStream; 32 | import java.util.Collections; 33 | import java.util.List; 34 | import java.util.Map; 35 | import java.util.Random; 36 | import java.util.Set; 37 | import org.slf4j.Logger; 38 | import org.slf4j.LoggerFactory; 39 | import com.google.common.hash.Hashing; 40 | import com.google.common.io.BaseEncoding; 41 | import static com.google.common.base.Preconditions.checkArgument; 42 | import static it.anyplace.sync.bep.BlockPusher.BLOCK_SIZE; 43 | import it.anyplace.sync.core.beans.FileBlocks; 44 | import it.anyplace.sync.core.cache.BlockCache; 45 | import java.io.Closeable; 46 | import java.util.concurrent.atomic.AtomicReference; 47 | import org.apache.commons.io.FileUtils; 48 | import static com.google.common.base.Preconditions.checkArgument; 49 | 50 | /** 51 | * 52 | * @author aleph 53 | */ 54 | public class BlockPuller { 55 | 56 | private BlockCache blockCache; 57 | private final Logger logger = LoggerFactory.getLogger(getClass()); 58 | private final ConfigurationService configuration; 59 | private final BlockExchangeConnectionHandler connectionHandler; 60 | private final Map blocksByHash = Maps.newConcurrentMap(); 61 | private final List hashList = Lists.newArrayList(); 62 | private final Set missingHashes = Sets.newConcurrentHashSet(); 63 | private final Set requestIds = Sets.newConcurrentHashSet(); 64 | private boolean closeConnection = false; 65 | 66 | public BlockPuller(ConfigurationService configuration, BlockExchangeConnectionHandler connectionHandler) { 67 | this.configuration = configuration; 68 | this.connectionHandler = connectionHandler; 69 | this.blockCache = BlockCache.getBlockCache(configuration); 70 | } 71 | 72 | public BlockPuller(ConfigurationService configuration, BlockExchangeConnectionHandler connectionHandler, boolean closeConnection) { 73 | this(configuration, connectionHandler); 74 | this.closeConnection = closeConnection; 75 | } 76 | 77 | public FileDownloadObserver pullBlocks(FileBlocks fileBlocks) throws InterruptedException { 78 | logger.info("pulling file = {}", fileBlocks); 79 | checkArgument(connectionHandler.hasFolder(fileBlocks.getFolder()), "supplied connection handler %s will not share folder %s", connectionHandler, fileBlocks.getFolder()); 80 | final Object lock = new Object(); 81 | final AtomicReference error = new AtomicReference<>(); 82 | final Object listener = new Object() { 83 | @Subscribe 84 | public void handleResponseMessageReceivedEvent(ResponseMessageReceivedEvent event) { 85 | synchronized (lock) { 86 | try { 87 | if (!requestIds.contains(event.getMessage().getId())) { 88 | return; 89 | } 90 | checkArgument(equal(event.getMessage().getCode(), ErrorCode.NO_ERROR), "received error response, code = %s", event.getMessage().getCode()); 91 | byte[] data = event.getMessage().getData().toByteArray(); 92 | String hash = BaseEncoding.base16().encode(Hashing.sha256().hashBytes(data).asBytes()); 93 | blockCache.pushBlock(data); 94 | if (missingHashes.remove(hash)) { 95 | blocksByHash.put(hash, data); 96 | logger.debug("aquired block, hash = {}", hash); 97 | lock.notify(); 98 | } else { 99 | logger.warn("received not-needed block, hash = {}", hash); 100 | } 101 | } catch (Exception ex) { 102 | error.set(ex); 103 | lock.notify(); 104 | } 105 | } 106 | } 107 | }; 108 | FileDownloadObserver fileDownloadObserver = new FileDownloadObserver() { 109 | 110 | private long getReceivedData() { 111 | return blocksByHash.size() * BLOCK_SIZE; 112 | } 113 | 114 | private long getTotalData() { 115 | return (blocksByHash.size() + missingHashes.size()) * BLOCK_SIZE; 116 | } 117 | 118 | @Override 119 | public double getProgress() { 120 | return isCompleted() ? 1d : getReceivedData() / ((double) getTotalData()); 121 | } 122 | 123 | @Override 124 | public String getProgressMessage() { 125 | return (Math.round(getProgress() * 1000d) / 10d) + "% " + FileUtils.byteCountToDisplaySize(getReceivedData()) + " / " + FileUtils.byteCountToDisplaySize(getTotalData()); 126 | } 127 | 128 | @Override 129 | public boolean isCompleted() { 130 | return missingHashes.isEmpty(); 131 | } 132 | 133 | @Override 134 | public void checkError() { 135 | if (error.get() != null) { 136 | throw new RuntimeException(error.get()); 137 | } 138 | } 139 | 140 | @Override 141 | public double waitForProgressUpdate() throws InterruptedException { 142 | if (!isCompleted()) { 143 | synchronized (lock) { 144 | checkError(); 145 | lock.wait(); 146 | checkError(); 147 | } 148 | } 149 | return getProgress(); 150 | } 151 | 152 | @Override 153 | public InputStream getInputStream() { 154 | checkArgument(missingHashes.isEmpty(), "pull failed, some blocks are still missing"); 155 | List blockList = Lists.newArrayList(Lists.transform(hashList, Functions.forMap(blocksByHash))); 156 | return new SequenceInputStream(Collections.enumeration(Lists.transform(blockList, new Function() { 157 | @Override 158 | public ByteArrayInputStream apply(byte[] data) { 159 | return new ByteArrayInputStream(data); 160 | } 161 | }))); 162 | } 163 | 164 | @Override 165 | public void close() { 166 | missingHashes.clear(); 167 | hashList.clear(); 168 | blocksByHash.clear(); 169 | try { 170 | connectionHandler.getEventBus().unregister(listener); 171 | } catch (Exception ex) { 172 | } 173 | if (closeConnection) { 174 | connectionHandler.close(); 175 | } 176 | } 177 | }; 178 | try { 179 | synchronized (lock) { 180 | hashList.addAll(Lists.transform(fileBlocks.getBlocks(), new Function() { 181 | @Override 182 | public String apply(BlockInfo block) { 183 | return block.getHash(); 184 | } 185 | })); 186 | missingHashes.addAll(hashList); 187 | for (String hash : missingHashes) { 188 | byte[] block = blockCache.pullBlock(hash); 189 | if (block != null) { 190 | blocksByHash.put(hash, block); 191 | missingHashes.remove(hash); 192 | } 193 | } 194 | connectionHandler.getEventBus().register(listener); 195 | for (BlockInfo block : fileBlocks.getBlocks()) { 196 | if (missingHashes.contains(block.getHash())) { 197 | int requestId = Math.abs(new Random().nextInt()); 198 | requestIds.add(requestId); 199 | connectionHandler.sendMessage(Request.newBuilder() 200 | .setId(requestId) 201 | .setFolder(fileBlocks.getFolder()) 202 | .setName(fileBlocks.getPath()) 203 | .setOffset(block.getOffset()) 204 | .setSize(block.getSize()) 205 | .setHash(ByteString.copyFrom(BaseEncoding.base16().decode(block.getHash()))) 206 | .build()); 207 | logger.debug("sent request for block, hash = {}", block.getHash()); 208 | } 209 | } 210 | return fileDownloadObserver; 211 | } 212 | } catch (Exception ex) { 213 | fileDownloadObserver.close(); 214 | throw ex; 215 | } 216 | } 217 | 218 | public abstract class FileDownloadObserver implements Closeable { 219 | 220 | public abstract void checkError(); 221 | 222 | public abstract double getProgress(); 223 | 224 | public abstract String getProgressMessage(); 225 | 226 | public abstract boolean isCompleted(); 227 | 228 | public abstract double waitForProgressUpdate() throws InterruptedException; 229 | 230 | public FileDownloadObserver waitForComplete() throws InterruptedException { 231 | while (!isCompleted()) { 232 | waitForProgressUpdate(); 233 | } 234 | return this; 235 | } 236 | 237 | public abstract InputStream getInputStream(); 238 | 239 | @Override 240 | public abstract void close(); 241 | 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /a-sync-bep/src/main/java/it/anyplace/sync/bep/FolderBrowser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.bep; 15 | 16 | import com.google.common.base.Function; 17 | import com.google.common.cache.CacheBuilder; 18 | import com.google.common.cache.CacheLoader; 19 | import com.google.common.cache.LoadingCache; 20 | import com.google.common.collect.Iterables; 21 | import com.google.common.collect.Lists; 22 | import com.google.common.eventbus.Subscribe; 23 | import it.anyplace.sync.core.beans.FolderInfo; 24 | import it.anyplace.sync.core.beans.FolderStats; 25 | import java.io.Closeable; 26 | import java.util.List; 27 | import org.apache.commons.lang3.tuple.Pair; 28 | import static com.google.common.base.Preconditions.checkNotNull; 29 | import it.anyplace.sync.core.interfaces.IndexRepository; 30 | 31 | /** 32 | * 33 | * @author aleph 34 | */ 35 | public final class FolderBrowser implements Closeable { 36 | 37 | private final IndexHandler indexHandler; 38 | private final LoadingCache folderStatsCache = CacheBuilder.newBuilder() 39 | .build(new CacheLoader() { 40 | @Override 41 | public FolderStats load(String folder) throws Exception { 42 | return FolderStats.newBuilder() 43 | .setFolder(folder) 44 | .build(); 45 | } 46 | }); 47 | private final Object indexRepositoryEventListener = new Object() { 48 | @Subscribe 49 | public void handleFolderStatsUpdatedEvent(IndexRepository.FolderStatsUpdatedEvent event) { 50 | addFolderStats(event.getFolderStats()); 51 | } 52 | }; 53 | 54 | protected FolderBrowser(IndexHandler indexHandler) { 55 | checkNotNull(indexHandler); 56 | this.indexHandler = indexHandler; 57 | indexHandler.getIndexRepository().getEventBus().register(indexRepositoryEventListener); 58 | addFolderStats(indexHandler.getIndexRepository().findAllFolderStats()); 59 | } 60 | 61 | private void addFolderStats(List folderStatsList) { 62 | for (FolderStats folderStats : folderStatsList) { 63 | folderStatsCache.put(folderStats.getFolder(), folderStats); 64 | } 65 | } 66 | 67 | public FolderStats getFolderStats(String folder) { 68 | return folderStatsCache.getUnchecked(folder); 69 | } 70 | 71 | public FolderInfo getFolderInfo(String folder) { 72 | return indexHandler.getFolderInfo(folder); 73 | } 74 | 75 | public List> getFolderInfoAndStatsList() { 76 | return Lists.newArrayList(Iterables.transform(indexHandler.getFolderInfoList(), new Function>() { 77 | @Override 78 | public Pair apply(FolderInfo folderInfo) { 79 | return Pair.of(folderInfo, getFolderStats(folderInfo.getFolder())); 80 | } 81 | })); 82 | } 83 | 84 | @Override 85 | public void close() { 86 | indexHandler.getIndexRepository().getEventBus().unregister(indexRepositoryEventListener); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /a-sync-bep/src/main/java/it/anyplace/sync/bep/IndexFinder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Davide Imbriaco . 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.bep; 15 | 16 | import static com.google.common.base.Objects.equal; 17 | import static com.google.common.base.Preconditions.checkArgument; 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | import com.google.common.collect.Lists; 20 | import com.google.common.collect.Queues; 21 | import com.google.common.eventbus.AsyncEventBus; 22 | import com.google.common.eventbus.EventBus; 23 | import com.google.common.eventbus.Subscribe; 24 | import it.anyplace.sync.core.beans.FileInfo; 25 | import it.anyplace.sync.core.interfaces.IndexRepository; 26 | import it.anyplace.sync.core.utils.ExecutorUtils; 27 | import static it.anyplace.sync.core.utils.FileInfoOrdering.ALPHA_ASC_DIR_FIRST; 28 | import java.io.Closeable; 29 | import java.util.Collections; 30 | import java.util.Comparator; 31 | import java.util.List; 32 | import java.util.Queue; 33 | import java.util.concurrent.ExecutorService; 34 | import java.util.concurrent.Executors; 35 | import java.util.concurrent.Future; 36 | import java.util.concurrent.atomic.AtomicReference; 37 | import org.apache.commons.lang3.StringUtils; 38 | import org.slf4j.Logger; 39 | import org.slf4j.LoggerFactory; 40 | 41 | public class IndexFinder implements Closeable { 42 | 43 | private final Logger logger = LoggerFactory.getLogger(getClass()); 44 | private Comparator ordering = ALPHA_ASC_DIR_FIRST; 45 | private final IndexRepository indexRepository; 46 | private final ExecutorService queryExecutorService = Executors.newSingleThreadExecutor(), eventProcessingService = Executors.newCachedThreadPool(); 47 | private final EventBus eventBus = new AsyncEventBus(eventProcessingService); 48 | private final boolean dropQueriesOnNewSubmit = true; 49 | private final int maxResults; 50 | // private final Queue runningQueries = Queues.newConcurrentLinkedQueue(); 51 | private Future previousQuery; 52 | 53 | private IndexFinder(IndexRepository indexRepository, int maxResults) { 54 | checkNotNull(indexRepository); 55 | checkArgument(maxResults > 0); 56 | this.indexRepository = indexRepository; 57 | this.maxResults = maxResults; 58 | } 59 | 60 | public IndexFinder setOrdering(Comparator ordering) { 61 | checkNotNull(ordering); 62 | this.ordering = ordering; 63 | return this; 64 | } 65 | 66 | public EventBus getEventBus() { 67 | return eventBus; 68 | } 69 | 70 | public synchronized IndexFinder submitSearch(final String query) { 71 | checkArgument(!StringUtils.isBlank(query), "query term cannot be blank"); 72 | logger.info("submitSearch, term = '{}'", query); 73 | if (dropQueriesOnNewSubmit == true && previousQuery != null) { 74 | // previousQuery.cancel(true); note: cannot interrupt. interrupt break h2 connection to db 75 | previousQuery.cancel(false); 76 | } 77 | previousQuery = queryExecutorService.submit(new Runnable() { 78 | @Override 79 | public void run() { 80 | try { 81 | logger.info("search file info for query = '{}'", query); 82 | final long count = indexRepository.countFileInfoBySearchTerm(query); 83 | final boolean hasTooManyResults = count > maxResults, hasGoodResults = count > 0 && count <= maxResults; 84 | final List list = hasGoodResults ? indexRepository.findFileInfoBySearchTerm(query) : null; 85 | // final List list = indexRepository.findFileInfoBySearchTerm(query); 86 | // final boolean hasTooManyResults = list.size() > maxResults, hasGoodResults = !list.isEmpty() && !hasTooManyResults; 87 | logger.info("got {} results for search term = '{}'", count, query); 88 | if (!eventProcessingService.isShutdown()) { 89 | eventBus.post(new SearchCompletedEvent() { 90 | @Override 91 | public String getQuery() { 92 | return query; 93 | } 94 | 95 | @Override 96 | public List getResultList() { 97 | checkNotNull(list, "this query has no good results (got either too many results or zero results)"); 98 | List res = Lists.newArrayList(list); 99 | Collections.sort(list, ordering); 100 | return res; 101 | } 102 | 103 | @Override 104 | public long getResultCount() { 105 | return count; 106 | } 107 | 108 | @Override 109 | public boolean hasZeroResults() { 110 | return count == 0; 111 | } 112 | 113 | @Override 114 | public boolean hasTooManyResults() { 115 | return hasTooManyResults; 116 | } 117 | 118 | @Override 119 | public boolean hasGoodResults() { 120 | return hasGoodResults; 121 | } 122 | }); 123 | } 124 | } catch (Exception ex) { 125 | if (Thread.currentThread().isInterrupted()) { 126 | logger.warn("interrupted search for term = '{}', ex = {}", query, ex); 127 | } else { 128 | logger.error("error running file info search by term = '{}'", query); 129 | logger.error("error running file info search by term", ex); 130 | } 131 | } 132 | } 133 | }); 134 | return this; 135 | } 136 | 137 | public SearchCompletedEvent doSearch(final String term) throws InterruptedException { 138 | final Object lock = new Object(); 139 | final AtomicReference reference = new AtomicReference<>(); 140 | final Object eventListener = new Object() { 141 | @Subscribe 142 | public void handleSearchCompletedEvent(SearchCompletedEvent event) { 143 | if (equal(event.getQuery(), term)) { 144 | synchronized (lock) { 145 | reference.set(event); 146 | lock.notify(); 147 | } 148 | } 149 | } 150 | }; 151 | synchronized (lock) { 152 | eventBus.register(eventListener); 153 | submitSearch(term); 154 | try { 155 | while (!Thread.currentThread().isInterrupted() && reference.get() == null) { 156 | lock.wait(); 157 | } 158 | checkNotNull(reference.get()); 159 | return reference.get(); 160 | } finally { 161 | eventBus.unregister(eventListener); 162 | } 163 | } 164 | } 165 | 166 | public interface SearchCompletedEvent { 167 | 168 | public String getQuery(); 169 | 170 | public long getResultCount(); 171 | 172 | public boolean hasZeroResults(); 173 | 174 | public boolean hasTooManyResults(); 175 | 176 | public boolean hasGoodResults(); 177 | 178 | public List getResultList(); 179 | } 180 | 181 | @Override 182 | public void close() { 183 | queryExecutorService.shutdown(); 184 | eventProcessingService.shutdown(); 185 | ExecutorUtils.awaitTerminationSafe(eventProcessingService); 186 | // ExecutorUtils.awaitTerminationSafe(executorService); note: not waiting for temination (fast close) 187 | } 188 | 189 | public static Builder newBuilder() { 190 | return new Builder(); 191 | } 192 | 193 | public static class Builder { 194 | 195 | private IndexRepository indexRepository; 196 | private int maxResults = 16; 197 | 198 | private Builder() { 199 | 200 | } 201 | 202 | public IndexRepository getIndexRepository() { 203 | return indexRepository; 204 | } 205 | 206 | public Builder setIndexRepository(IndexRepository indexRepository) { 207 | this.indexRepository = indexRepository; 208 | return this; 209 | } 210 | 211 | public int getMaxResults() { 212 | return maxResults; 213 | } 214 | 215 | public Builder setMaxResults(int maxResults) { 216 | this.maxResults = maxResults; 217 | return this; 218 | } 219 | 220 | public IndexFinder build() { 221 | return new IndexFinder(indexRepository, maxResults); 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /a-sync-bep/src/main/java/it/anyplace/sync/bep/beans/ClusterConfigFolderInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.bep.beans; 15 | 16 | import static com.google.common.base.MoreObjects.firstNonNull; 17 | import static com.google.common.base.Preconditions.checkNotNull; 18 | import static com.google.common.base.Strings.emptyToNull; 19 | import javax.annotation.Nullable; 20 | 21 | /** 22 | * 23 | * @author aleph 24 | */ 25 | public class ClusterConfigFolderInfo { 26 | 27 | private final String folder; 28 | private final String label; 29 | private final boolean announced; 30 | private final boolean shared; 31 | 32 | private ClusterConfigFolderInfo(String folder, @Nullable String label, boolean announced, boolean shared) { 33 | checkNotNull(emptyToNull(folder)); 34 | this.folder = folder; 35 | this.label = firstNonNull(emptyToNull(label), folder); 36 | this.announced = announced; 37 | this.shared = shared; 38 | } 39 | 40 | public String getFolder() { 41 | return folder; 42 | } 43 | 44 | public String getLabel() { 45 | return label; 46 | } 47 | 48 | public boolean isAnnounced() { 49 | return announced; 50 | } 51 | 52 | public boolean isShared() { 53 | return shared; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "ClusterConfigFolderInfo{" + "folder=" + folder + ", label=" + label + ", announced=" + announced + ", shared=" + shared + '}'; 59 | } 60 | 61 | public static Builder newBuilder() { 62 | return new Builder(); 63 | } 64 | 65 | public static class Builder { 66 | 67 | private String folder, label; 68 | private boolean announced = false, shared = false; 69 | 70 | private Builder() { 71 | } 72 | 73 | public String getFolder() { 74 | return folder; 75 | } 76 | 77 | public Builder setFolder(String folder) { 78 | this.folder = folder; 79 | return this; 80 | } 81 | 82 | public String getLabel() { 83 | return label; 84 | } 85 | 86 | public Builder setLabel(String label) { 87 | this.label = label; 88 | return this; 89 | } 90 | 91 | public boolean isAnnounced() { 92 | return announced; 93 | } 94 | 95 | public Builder setAnnounced(boolean announced) { 96 | this.announced = announced; 97 | return this; 98 | } 99 | 100 | public boolean isShared() { 101 | return shared; 102 | } 103 | 104 | public Builder setShared(boolean shared) { 105 | this.shared = shared; 106 | return this; 107 | } 108 | 109 | public ClusterConfigFolderInfo build() { 110 | return new ClusterConfigFolderInfo(folder, label, announced, shared); 111 | } 112 | 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /a-sync-bep/src/main/resources/blockExchageProtos.proto: -------------------------------------------------------------------------------- 1 | package it.anyplace.sync.bep.protos; 2 | 3 | message Hello { 4 | optional string device_name = 1; 5 | optional string client_name = 2; 6 | optional string client_version = 3; 7 | } 8 | 9 | message Header { 10 | optional MessageType type = 1; 11 | optional MessageCompression compression = 2; 12 | } 13 | 14 | enum MessageType { 15 | CLUSTER_CONFIG = 0; 16 | INDEX = 1; 17 | INDEX_UPDATE = 2; 18 | REQUEST = 3; 19 | RESPONSE = 4; 20 | DOWNLOAD_PROGRESS = 5; 21 | PING = 6; 22 | CLOSE = 7; 23 | } 24 | 25 | enum MessageCompression { 26 | NONE = 0; 27 | LZ4 = 1; 28 | } 29 | 30 | message ClusterConfig { 31 | repeated Folder folders = 1; 32 | } 33 | 34 | message Folder { 35 | optional string id = 1; 36 | optional string label = 2; 37 | optional bool read_only = 3; 38 | optional bool ignore_permissions = 4; 39 | optional bool ignore_delete = 5; 40 | optional bool disable_temp_indexes = 6; 41 | 42 | repeated Device devices = 16; 43 | } 44 | 45 | message Device { 46 | optional bytes id = 1; 47 | optional string name = 2; 48 | repeated string addresses = 3; 49 | optional Compression compression = 4; 50 | optional string cert_name = 5; 51 | optional int64 max_sequence = 6; 52 | optional bool introducer = 7; 53 | optional uint64 index_id = 8; 54 | } 55 | 56 | enum Compression { 57 | METADATA = 0; 58 | NEVER = 1; 59 | ALWAYS = 2; 60 | } 61 | 62 | message Index { 63 | optional string folder = 1; 64 | repeated FileInfo files = 2; 65 | } 66 | 67 | message IndexUpdate { 68 | optional string folder = 1; 69 | repeated FileInfo files = 2; 70 | } 71 | 72 | message FileInfo { 73 | optional string name = 1; 74 | optional FileInfoType type = 2; 75 | optional int64 size = 3; 76 | optional uint32 permissions = 4; 77 | optional int64 modified_s = 5; 78 | optional int32 modified_ns = 11; 79 | optional bool deleted = 6; 80 | optional bool invalid = 7; 81 | optional bool no_permissions = 8; 82 | optional Vector version = 9; 83 | optional int64 sequence = 10; 84 | 85 | repeated BlockInfo Blocks = 16; 86 | } 87 | 88 | enum FileInfoType { 89 | FILE = 0; 90 | DIRECTORY = 1; 91 | SYMLINK_FILE = 2; 92 | SYMLINK_DIRECTORY = 3; 93 | SYMLINK_UNKNOWN = 4; 94 | } 95 | 96 | message BlockInfo { 97 | optional int64 offset = 1; 98 | optional int32 size = 2; 99 | optional bytes hash = 3; 100 | } 101 | 102 | message Vector { 103 | repeated Counter counters = 1; 104 | } 105 | 106 | message Counter { 107 | optional uint64 id = 1; 108 | optional uint64 value = 2; 109 | } 110 | 111 | message Request { 112 | optional int32 id = 1; 113 | optional string folder = 2; 114 | optional string name = 3; 115 | optional int64 offset = 4; 116 | optional int32 size = 5; 117 | optional bytes hash = 6; 118 | optional bool from_temporary = 7; 119 | } 120 | 121 | message Response { 122 | optional int32 id = 1; 123 | optional bytes data = 2; 124 | optional ErrorCode code = 3; 125 | } 126 | 127 | enum ErrorCode { 128 | NO_ERROR = 0; 129 | GENERIC = 1; 130 | NO_SUCH_FILE = 2; 131 | INVALID_FILE = 3; 132 | } 133 | 134 | message DownloadProgress { 135 | optional string folder = 1; 136 | repeated FileDownloadProgressUpdate updates = 2; 137 | } 138 | 139 | message FileDownloadProgressUpdate { 140 | optional FileDownloadProgressUpdateType update_type = 1; 141 | optional string name = 2; 142 | optional Vector version = 3; 143 | repeated int32 block_indexes = 4; 144 | } 145 | 146 | enum FileDownloadProgressUpdateType { 147 | APPEND = 0; 148 | FORGET = 1; 149 | } 150 | 151 | message Ping { 152 | } 153 | 154 | message Close { 155 | optional string reason = 1; 156 | } -------------------------------------------------------------------------------- /a-sync-bep/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /a-sync-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | it.anyplace.sync 6 | a-sync-parent 7 | 1.3 8 | ../a-sync-parent 9 | 10 | a-sync-client 11 | jar 12 | 13 | 14 | 15 | ${project.groupId} 16 | a-sync-core 17 | ${project.version} 18 | 19 | 20 | ${project.groupId} 21 | a-sync-bep 22 | ${project.version} 23 | 24 | 25 | ${project.groupId} 26 | a-sync-repository 27 | ${project.version} 28 | 29 | 30 | ${project.groupId} 31 | a-sync-discovery 32 | ${project.version} 33 | 34 | 35 | 36 | 37 | 38 | 39 | maven-compiler-plugin 40 | 3.6.0 41 | 42 | 1.7 43 | 1.7 44 | 45 | 46 | 47 | org.apache.maven.plugins 48 | maven-shade-plugin 49 | 2.4.1 50 | 51 | 52 | 53 | 54 | package 55 | 56 | shade 57 | 58 | 59 | 60 | 61 | it.anyplace.sync.client.Main 62 | 63 | 64 | 65 | 66 | *:* 67 | 68 | META-INF/*.SF 69 | META-INF/*.DSA 70 | META-INF/*.RSA 71 | 72 | 73 | 74 | true 75 | executable 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /a-sync-client/src/main/java/it/anyplace/sync/devices/DevicesHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Davide Imbriaco . 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.devices; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | import com.google.common.collect.Lists; 18 | import com.google.common.collect.Maps; 19 | import com.google.common.eventbus.EventBus; 20 | import com.google.common.eventbus.Subscribe; 21 | import it.anyplace.sync.core.configuration.ConfigurationService; 22 | import it.anyplace.sync.core.beans.DeviceAddress; 23 | import it.anyplace.sync.core.beans.DeviceInfo; 24 | import it.anyplace.sync.core.beans.DeviceStats; 25 | import it.anyplace.sync.core.beans.DeviceStats.DeviceStatus; 26 | import it.anyplace.sync.core.events.DeviceAddressActiveEvent; 27 | import it.anyplace.sync.core.events.DeviceAddressReceivedEvent; 28 | import it.anyplace.sync.core.utils.ExecutorUtils; 29 | import java.io.Closeable; 30 | import java.util.Collection; 31 | import java.util.Collections; 32 | import java.util.Date; 33 | import java.util.List; 34 | import java.util.Map; 35 | import java.util.concurrent.Executors; 36 | import java.util.concurrent.ScheduledExecutorService; 37 | import java.util.concurrent.TimeUnit; 38 | import org.slf4j.Logger; 39 | import org.slf4j.LoggerFactory; 40 | import static com.google.common.base.Preconditions.checkNotNull; 41 | 42 | public class DevicesHandler implements Closeable { 43 | 44 | private final Logger logger = LoggerFactory.getLogger(getClass()); 45 | private final Map deviceStatsMap = Collections.synchronizedMap(Maps.newHashMap()); 46 | private final ConfigurationService configuration; 47 | private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); 48 | private final EventBus eventBus = new EventBus(); 49 | 50 | public DevicesHandler(ConfigurationService configuration) { 51 | checkNotNull(configuration); 52 | this.configuration = configuration; 53 | loadDevicesFromConfiguration(); 54 | scheduledExecutorService.scheduleAtFixedRate(new Runnable() { 55 | @Override 56 | public void run() { 57 | for (DeviceStats deviceStats : Lists.newArrayList(deviceStatsMap.values())) { 58 | switch (deviceStats.getStatus()) { 59 | case ONLINE_ACTIVE: 60 | if (new Date().getTime() - deviceStats.getLastActive().getTime() > 5 * 1000) { 61 | pushDeviceStats(deviceStats.copyBuilder().setStatus(DeviceStatus.ONLINE_INACTIVE).build()); 62 | } 63 | break; 64 | } 65 | } 66 | } 67 | }, 5, 5, TimeUnit.SECONDS); 68 | } 69 | 70 | @Subscribe 71 | public void handleDeviceAddressActiveEvent(DeviceAddressActiveEvent event) { 72 | pushDeviceStats(getDeviceStats(event.getDeviceAddress().getDeviceId()) 73 | .copyBuilder() 74 | .setLastActive(new Date()) 75 | .setStatus(DeviceStats.DeviceStatus.ONLINE_ACTIVE) 76 | .build()); 77 | } 78 | 79 | @Subscribe 80 | public void handleDeviceAddressReceivedEvent(DeviceAddressReceivedEvent event) { 81 | for (DeviceAddress deviceAddress : event.getDeviceAddresses()) { 82 | if (deviceAddress.isWorking()) { 83 | DeviceStats deviceStats = getDeviceStats(deviceAddress.getDeviceId()); 84 | DeviceStatus newStatus; 85 | switch (deviceStats.getStatus()) { 86 | case OFFLINE: 87 | newStatus = DeviceStatus.ONLINE_INACTIVE; 88 | break; 89 | default: 90 | newStatus = deviceStats.getStatus(); 91 | } 92 | pushDeviceStats(deviceStats.copyBuilder().setStatus(newStatus).setLastSeen(new Date()).build()); 93 | } 94 | } 95 | } 96 | 97 | public void clear() { 98 | deviceStatsMap.clear(); 99 | } 100 | 101 | public EventBus getEventBus() { 102 | return eventBus; 103 | } 104 | 105 | private void loadDevicesFromConfiguration() { 106 | for (DeviceInfo deviceInfo : configuration.getPeers()) { 107 | if (!deviceStatsMap.containsKey(deviceInfo.getDeviceId())) { 108 | pushDeviceStats(DeviceStats.newBuilder().setDeviceId(deviceInfo.getDeviceId()).setName(deviceInfo.getName()).build()); 109 | } 110 | } 111 | } 112 | 113 | public DeviceStats getDeviceStats(String deviceId) { 114 | loadDevicesFromConfiguration(); 115 | DeviceStats deviceStats = deviceStatsMap.get(deviceId); 116 | if (deviceStats == null) { 117 | pushDeviceStats(deviceStats = DeviceStats.newBuilder().setDeviceId(deviceId).build()); 118 | } 119 | return deviceStats; 120 | } 121 | 122 | public Collection getDeviceStatsList() { 123 | loadDevicesFromConfiguration(); 124 | return Collections.unmodifiableCollection(deviceStatsMap.values()); 125 | } 126 | 127 | private void pushDeviceStats(final DeviceStats deviceStats) { 128 | deviceStatsMap.put(deviceStats.getDeviceId(), deviceStats); 129 | eventBus.post(new DeviceStatsUpdateEvent() { 130 | @Override 131 | public List getChangedDeviceStats() { 132 | return Collections.singletonList(deviceStats); 133 | } 134 | }); 135 | } 136 | 137 | public interface DeviceStatsUpdateEvent { 138 | 139 | public List getChangedDeviceStats(); 140 | } 141 | 142 | @Override 143 | public void close() { 144 | scheduledExecutorService.shutdown(); 145 | ExecutorUtils.awaitTerminationSafe(scheduledExecutorService); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /a-sync-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | it.anyplace.sync 6 | a-sync-parent 7 | 1.3 8 | ../a-sync-parent 9 | 10 | a-sync-core 11 | jar 12 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/beans/BlockInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.beans; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | 18 | /** 19 | * 20 | * @author aleph 21 | */ 22 | public class BlockInfo { 23 | 24 | private final long offset; 25 | private final int size; 26 | private final String hash; 27 | 28 | public BlockInfo(long offset, int size, String hash) { 29 | checkNotNull(hash); 30 | this.offset = offset; 31 | this.size = size; 32 | this.hash = hash; 33 | } 34 | 35 | public long getOffset() { 36 | return offset; 37 | } 38 | 39 | public int getSize() { 40 | return size; 41 | } 42 | 43 | public String getHash() { 44 | return hash; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/beans/DeviceAddress.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.beans; 15 | 16 | import static com.google.common.base.MoreObjects.firstNonNull; 17 | import static com.google.common.base.Objects.equal; 18 | import com.google.common.base.Preconditions; 19 | import com.google.common.base.Predicate; 20 | import com.google.common.base.Strings; 21 | import static com.google.common.base.Strings.emptyToNull; 22 | import com.google.common.collect.ImmutableMap; 23 | import com.google.common.collect.Iterables; 24 | import java.net.InetAddress; 25 | import java.net.InetSocketAddress; 26 | import java.net.URI; 27 | import java.net.UnknownHostException; 28 | import java.nio.charset.StandardCharsets; 29 | import java.util.Date; 30 | import java.util.Map; 31 | import java.util.Objects; 32 | import javax.annotation.Nullable; 33 | import org.apache.http.NameValuePair; 34 | import org.apache.http.client.utils.URLEncodedUtils; 35 | import org.slf4j.Logger; 36 | import org.slf4j.LoggerFactory; 37 | 38 | /** 39 | * 40 | * @author aleph 41 | */ 42 | public class DeviceAddress { 43 | 44 | private final static Logger logger = LoggerFactory.getLogger(DeviceAddress.class); 45 | private final String deviceId; 46 | private final Long instanceId; 47 | private final String address; 48 | private final AddressProducer producer; 49 | private final int score; 50 | private final Date lastModified; 51 | 52 | private DeviceAddress(String deviceId, @Nullable Long instanceId, String address, AddressProducer producer, @Nullable Integer score, @Nullable Date lastModified) { 53 | this.deviceId = deviceId; 54 | this.instanceId = instanceId; 55 | this.address = address; 56 | this.producer = firstNonNull(producer, AddressProducer.UNKNOWN); 57 | this.score = firstNonNull(score, Integer.MAX_VALUE); 58 | this.lastModified = firstNonNull(lastModified, new Date()); 59 | } 60 | 61 | public DeviceAddress(String deviceId, String address) { 62 | this(deviceId, null, address, null, null, null); 63 | } 64 | 65 | public Date getLastModified() { 66 | return lastModified; 67 | } 68 | 69 | public String getDeviceId() { 70 | return deviceId; 71 | } 72 | 73 | public Long getInstanceId() { 74 | return instanceId; 75 | } 76 | 77 | public String getAddress() { 78 | return address; 79 | } 80 | 81 | public AddressProducer getProducer() { 82 | return producer; 83 | } 84 | 85 | public int getScore() { 86 | return score; 87 | } 88 | 89 | public InetAddress getInetAddress() { 90 | try { 91 | return InetAddress.getByName(address.replaceFirst("^[^:]+://", "").replaceFirst("(:[0-9]+)?(/.*)?$", "")); 92 | } catch (UnknownHostException ex) { 93 | throw new RuntimeException(ex); 94 | } 95 | } 96 | 97 | public int getPort() { 98 | if (address.matches("^[a-z]+://[^:]+:([0-9]+).*")) { 99 | return Integer.parseInt(address.replaceFirst("^[a-z]+://[^:]+:([0-9]+).*", "$1")); 100 | } else { 101 | return DEFAULT_PORT_BY_PROTOCOL.get(getType()); 102 | } 103 | } 104 | private static final Map DEFAULT_PORT_BY_PROTOCOL = ImmutableMap.builder() 105 | .put(AddressType.TCP, 22000) 106 | .put(AddressType.RELAY, 22067) 107 | .put(AddressType.HTTP_RELAY, 80) 108 | .put(AddressType.HTTPS_RELAY, 443) 109 | .build(); 110 | 111 | public AddressType getType() { 112 | if (Strings.isNullOrEmpty(address)) { 113 | return AddressType.NULL; 114 | } else if (address.startsWith("tcp://")) { 115 | return AddressType.TCP; 116 | } else if (address.startsWith("relay://")) { 117 | return AddressType.RELAY; 118 | } else if (address.startsWith("relay-http://")) { 119 | return AddressType.HTTP_RELAY; 120 | } else if (address.startsWith("relay-https://")) { 121 | return AddressType.HTTPS_RELAY; 122 | } else { 123 | return AddressType.OTHER; 124 | } 125 | } 126 | 127 | public InetSocketAddress getSocketAddress() { 128 | return new InetSocketAddress(getInetAddress(), getPort()); 129 | } 130 | 131 | public boolean isWorking() { 132 | return score < Integer.MAX_VALUE; 133 | } 134 | 135 | public boolean isTcp() { 136 | return equal(getType(), AddressType.TCP); 137 | } 138 | 139 | public boolean containsUriParam(String key) { 140 | return getUriParam(key) != null; 141 | } 142 | 143 | public boolean containsUriParamValue(String key) { 144 | return !Strings.isNullOrEmpty(getUriParam(key)); 145 | } 146 | 147 | public URI getUriSafe() { 148 | try { 149 | return URI.create(getAddress()); 150 | } catch (Exception ex) { 151 | logger.warn("processing invalid url = {}, ex = {}; stripping params", getAddress(), ex.toString()); 152 | return URI.create(getAddress().replaceFirst("^([^/]+://[^/]+)(/.*)?$", "$1")); 153 | } 154 | } 155 | 156 | public @Nullable 157 | String getUriParam(final String key) { 158 | Preconditions.checkNotNull(emptyToNull(key)); 159 | try { 160 | NameValuePair record = Iterables.find(URLEncodedUtils.parse(getUriSafe(), StandardCharsets.UTF_8.name()), new Predicate() { 161 | @Override 162 | public boolean apply(NameValuePair input) { 163 | return equal(input.getName(), key); 164 | } 165 | }, null); 166 | return record == null ? null : record.getValue(); 167 | } catch (Exception ex) { 168 | logger.warn("ex", ex); 169 | return null; 170 | } 171 | } 172 | 173 | public enum AddressType { 174 | TCP, RELAY, OTHER, NULL, HTTP_RELAY, HTTPS_RELAY 175 | } 176 | 177 | public enum AddressProducer { 178 | LOCAL_DISCOVERY, GLOBAL_DISCOVERY, UNKNOWN 179 | } 180 | 181 | @Override 182 | public String toString() { 183 | return "DeviceAddress{" + "deviceId=" + deviceId + ", instanceId=" + instanceId + ", address=" + address + '}'; 184 | } 185 | 186 | @Override 187 | public int hashCode() { 188 | int hash = 3; 189 | hash = 29 * hash + Objects.hashCode(this.deviceId); 190 | hash = 29 * hash + Objects.hashCode(this.address); 191 | return hash; 192 | } 193 | 194 | @Override 195 | public boolean equals(Object obj) { 196 | if (this == obj) { 197 | return true; 198 | } 199 | if (obj == null) { 200 | return false; 201 | } 202 | if (getClass() != obj.getClass()) { 203 | return false; 204 | } 205 | final DeviceAddress other = (DeviceAddress) obj; 206 | if (!Objects.equals(this.deviceId, other.deviceId)) { 207 | return false; 208 | } 209 | if (!Objects.equals(this.address, other.address)) { 210 | return false; 211 | } 212 | return true; 213 | } 214 | 215 | public static Builder newBuilder() { 216 | return new Builder(); 217 | } 218 | 219 | public Builder copyBuilder() { 220 | return new Builder(deviceId, instanceId, address, producer, score, lastModified); 221 | } 222 | 223 | public static class Builder { 224 | 225 | private String deviceId; 226 | private Long instanceId; 227 | private String address; 228 | private AddressProducer producer; 229 | private Integer score; 230 | private Date lastModified; 231 | 232 | private Builder() { 233 | } 234 | 235 | private Builder(String deviceId, Long instanceId, String address, AddressProducer producer, Integer score, Date lastModified) { 236 | this.deviceId = deviceId; 237 | this.instanceId = instanceId; 238 | this.address = address; 239 | this.producer = producer; 240 | this.score = score; 241 | this.lastModified = lastModified; 242 | } 243 | 244 | public Date getLastModified() { 245 | return lastModified; 246 | } 247 | 248 | public Builder setLastModified(Date lastModified) { 249 | this.lastModified = lastModified; 250 | return this; 251 | } 252 | 253 | public String getDeviceId() { 254 | return deviceId; 255 | } 256 | 257 | public Builder setDeviceId(String deviceId) { 258 | this.deviceId = deviceId; 259 | return this; 260 | } 261 | 262 | public Long getInstanceId() { 263 | return instanceId; 264 | } 265 | 266 | public Builder setInstanceId(Long instanceId) { 267 | this.instanceId = instanceId; 268 | return this; 269 | } 270 | 271 | public String getAddress() { 272 | return address; 273 | } 274 | 275 | public Builder setAddress(String address) { 276 | this.address = address; 277 | return this; 278 | } 279 | 280 | public AddressProducer getProducer() { 281 | return producer; 282 | } 283 | 284 | public Builder setProducer(AddressProducer producer) { 285 | this.producer = producer; 286 | return this; 287 | } 288 | 289 | public Integer getScore() { 290 | return score; 291 | } 292 | 293 | public Builder setScore(Integer score) { 294 | this.score = score; 295 | return this; 296 | } 297 | 298 | public DeviceAddress build() { 299 | return new DeviceAddress(deviceId, instanceId, address, producer, score, lastModified); 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/beans/DeviceInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Davide Imbriaco . 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.beans; 15 | 16 | import it.anyplace.sync.core.security.KeystoreHandler; 17 | import javax.annotation.Nullable; 18 | import static org.apache.commons.lang3.StringUtils.isBlank; 19 | 20 | public class DeviceInfo { 21 | 22 | private final String deviceId; 23 | private final String name; 24 | 25 | public DeviceInfo(String deviceId, @Nullable String name) { 26 | KeystoreHandler.validateDeviceId(deviceId); 27 | this.deviceId = deviceId; 28 | this.name = isBlank(name) ? deviceId.substring(0, 7) : name; 29 | } 30 | 31 | public String getDeviceId() { 32 | return deviceId; 33 | } 34 | 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "DeviceInfo{" + "deviceId=" + deviceId + ", name=" + name + '}'; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/beans/DeviceStats.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Davide Imbriaco . 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.beans; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | import static com.google.common.base.Strings.emptyToNull; 18 | import java.util.Date; 19 | import static org.apache.commons.lang3.StringUtils.isBlank; 20 | 21 | public class DeviceStats { 22 | 23 | private final String deviceId, name; 24 | 25 | private final Date lastActive, lastSeen; 26 | 27 | private final DeviceStatus status; 28 | 29 | private DeviceStats(String deviceId, String name, Date lastActive, Date lastSeen, DeviceStatus status) { 30 | checkNotNull(emptyToNull(deviceId)); 31 | checkNotNull(status); 32 | checkNotNull(lastActive); 33 | checkNotNull(lastSeen); 34 | this.deviceId = deviceId; 35 | this.name = isBlank(name) ? deviceId.substring(0, 7) : name; 36 | this.lastActive = lastActive; 37 | this.lastSeen = lastSeen; 38 | this.status = status; 39 | } 40 | 41 | public String getDeviceId() { 42 | return deviceId; 43 | } 44 | 45 | public String getName() { 46 | return name; 47 | } 48 | 49 | public Date getLastActive() { 50 | return lastActive; 51 | } 52 | 53 | public Date getLastSeen() { 54 | return lastSeen; 55 | } 56 | 57 | public DeviceStatus getStatus() { 58 | return status; 59 | } 60 | 61 | public static Builder newBuilder() { 62 | return new Builder(); 63 | } 64 | 65 | public Builder copyBuilder() { 66 | return new Builder(deviceId, name, lastActive, lastSeen, status); 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return "DeviceStats{" + "deviceId=" + deviceId + ", name=" + name + ", status=" + status + '}'; 72 | } 73 | 74 | public static enum DeviceStatus { 75 | OFFLINE, ONLINE_ACTIVE, ONLINE_INACTIVE 76 | } 77 | 78 | public static class Builder { 79 | 80 | private String deviceId, name; 81 | 82 | private Date lastActive = new Date(0), lastSeen = new Date(0); 83 | 84 | private DeviceStatus status = DeviceStatus.OFFLINE; 85 | 86 | private Builder() { 87 | } 88 | 89 | private Builder(String deviceId, String name, Date lastActive, Date lastSeen, DeviceStatus status) { 90 | this.deviceId = deviceId; 91 | this.name = name; 92 | this.lastActive = lastActive; 93 | this.lastSeen = lastSeen; 94 | this.status = status; 95 | } 96 | 97 | public String getDeviceId() { 98 | return deviceId; 99 | } 100 | 101 | public Builder setDeviceId(String deviceId) { 102 | this.deviceId = deviceId; 103 | return this; 104 | } 105 | 106 | public String getName() { 107 | return name; 108 | } 109 | 110 | public Builder setName(String name) { 111 | this.name = name; 112 | return this; 113 | } 114 | 115 | public Date getLastActive() { 116 | return lastActive; 117 | } 118 | 119 | public Builder setLastActive(Date lastActive) { 120 | this.lastActive = lastActive; 121 | return this; 122 | } 123 | 124 | public Date getLastSeen() { 125 | return lastSeen; 126 | } 127 | 128 | public Builder setLastSeen(Date lastSeen) { 129 | this.lastSeen = lastSeen; 130 | return this; 131 | } 132 | 133 | public DeviceStatus getStatus() { 134 | return status; 135 | } 136 | 137 | public Builder setStatus(DeviceStatus status) { 138 | this.status = status; 139 | return this; 140 | } 141 | 142 | public DeviceStats build() { 143 | return new DeviceStats(deviceId, name, lastActive, lastSeen, status); 144 | } 145 | 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/beans/FileBlocks.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.beans; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | import static com.google.common.base.Strings.emptyToNull; 18 | import com.google.common.collect.Lists; 19 | import it.anyplace.sync.core.utils.BlockUtils; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | /** 24 | * 25 | * @author aleph 26 | */ 27 | public class FileBlocks { 28 | 29 | private final List blocks; 30 | private final String hash, folder, path; 31 | private final long size; 32 | 33 | public FileBlocks(String folder, String path, Iterable blocks) { 34 | checkNotNull(emptyToNull(folder)); 35 | checkNotNull(emptyToNull(path)); 36 | checkNotNull(blocks); 37 | this.folder = folder; 38 | this.path = path; 39 | this.blocks = Collections.unmodifiableList(Lists.newArrayList(blocks)); 40 | long num = 0; 41 | for (BlockInfo block : blocks) { 42 | num += block.getSize(); 43 | } 44 | this.size = num; 45 | this.hash = BlockUtils.hashBlocks(this.blocks); 46 | } 47 | 48 | public List getBlocks() { 49 | return blocks; 50 | } 51 | 52 | public String getHash() { 53 | return hash; 54 | } 55 | 56 | public long getSize() { 57 | return size; 58 | } 59 | 60 | public String getFolder() { 61 | return folder; 62 | } 63 | 64 | public String getPath() { 65 | return path; 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return "FileBlocks{" + "blocks=" + blocks.size() + ", hash=" + hash + ", folder=" + folder + ", path=" + path + ", size=" + size + '}'; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/beans/FileInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.beans; 15 | 16 | import com.google.common.base.MoreObjects; 17 | import static com.google.common.base.Objects.equal; 18 | import static com.google.common.base.Preconditions.checkArgument; 19 | import com.google.common.base.Strings; 20 | import java.util.Date; 21 | import com.google.common.collect.Lists; 22 | import java.util.Collections; 23 | import java.util.List; 24 | import com.google.common.collect.Iterables; 25 | import it.anyplace.sync.core.utils.PathUtils; 26 | import static it.anyplace.sync.core.utils.PathUtils.PARENT_PATH; 27 | import javax.annotation.Nullable; 28 | import static it.anyplace.sync.core.utils.PathUtils.ROOT_PATH; 29 | import static com.google.common.base.Preconditions.checkNotNull; 30 | import static com.google.common.base.Strings.emptyToNull; 31 | import org.apache.commons.io.FileUtils; 32 | 33 | /** 34 | * 35 | * @author aleph 36 | */ 37 | public class FileInfo { 38 | 39 | private final String folder, fileName, path, parent, hash; 40 | private final Long size; 41 | private final Date lastModified; 42 | private final FileType type; 43 | private final List versionList; 44 | private final boolean deleted; 45 | 46 | private FileInfo(String folder, FileType type, String path, @Nullable Long size, @Nullable Date lastModified, @Nullable String hash, @Nullable List versionList, boolean deleted) { 47 | checkNotNull(Strings.emptyToNull(folder)); 48 | checkNotNull(path);//allow empty for 'fake' root path 49 | checkNotNull(type); 50 | this.folder = folder; 51 | this.type = type; 52 | this.path = path; 53 | if (PathUtils.isParent(path)) { 54 | this.fileName = PARENT_PATH; 55 | this.parent = ROOT_PATH; 56 | } else { 57 | this.fileName = PathUtils.getFileName(path); 58 | this.parent = PathUtils.isRoot(path) ? ROOT_PATH : PathUtils.getParentPath(path); 59 | } 60 | this.lastModified = MoreObjects.firstNonNull(lastModified, new Date(0)); 61 | if (type.equals(FileType.DIRECTORY)) { 62 | this.size = null; 63 | this.hash = null; 64 | } else { 65 | checkNotNull(size); 66 | checkNotNull(emptyToNull(hash)); 67 | this.size = size; 68 | this.hash = hash; 69 | } 70 | this.versionList = Collections.unmodifiableList(MoreObjects.firstNonNull(versionList, Collections.emptyList())); 71 | this.deleted = deleted; 72 | } 73 | 74 | public boolean isDeleted() { 75 | return deleted; 76 | } 77 | 78 | public String getFolder() { 79 | return folder; 80 | } 81 | 82 | public String getPath() { 83 | return path; 84 | } 85 | 86 | public String getFileName() { 87 | return fileName; 88 | } 89 | 90 | public String getParent() { 91 | return parent; 92 | } 93 | 94 | public Long getSize() { 95 | return size; 96 | } 97 | 98 | public Date getLastModified() { 99 | return lastModified; 100 | } 101 | 102 | public FileType getType() { 103 | return type; 104 | } 105 | 106 | public enum FileType { 107 | FILE, DIRECTORY 108 | } 109 | 110 | public String getHash() { 111 | return hash; 112 | } 113 | 114 | public List getVersionList() { 115 | return versionList; 116 | } 117 | 118 | public String describeSize() { 119 | return isFile() ? FileUtils.byteCountToDisplaySize(getSize()) : ""; 120 | } 121 | 122 | @Override 123 | public String toString() { 124 | return "FileRecord{" + "folder=" + folder + ", path=" + path + ", size=" + size + ", lastModified=" + lastModified + ", type=" + type + ", last version = " + Iterables.getLast(versionList, null) + '}'; 125 | } 126 | 127 | public boolean isDirectory() { 128 | return equal(getType(), FileType.DIRECTORY); 129 | } 130 | 131 | public boolean isFile() { 132 | return equal(getType(), FileType.FILE); 133 | } 134 | 135 | public static class Version { 136 | 137 | private final long id, value; 138 | 139 | public Version(long id, long value) { 140 | this.id = id; 141 | this.value = value; 142 | } 143 | 144 | public long getId() { 145 | return id; 146 | } 147 | 148 | public long getValue() { 149 | return value; 150 | } 151 | 152 | @Override 153 | public String toString() { 154 | return "Version{" + "id=" + id + ", value=" + value + '}'; 155 | } 156 | 157 | } 158 | 159 | public static Builder newBuilder() { 160 | return new Builder(); 161 | } 162 | 163 | public static class Builder { 164 | 165 | private String folder, path, hash; 166 | private Long size; 167 | private Date lastModified = new Date(0); 168 | private FileType type; 169 | private List versionList; 170 | private boolean deleted = false; 171 | 172 | private Builder() { 173 | } 174 | 175 | public String getFolder() { 176 | return folder; 177 | } 178 | 179 | public Builder setFolder(String folder) { 180 | this.folder = folder; 181 | return this; 182 | } 183 | 184 | public String getPath() { 185 | return path; 186 | } 187 | 188 | public Builder setPath(String path) { 189 | this.path = path; 190 | return this; 191 | } 192 | 193 | public Long getSize() { 194 | return size; 195 | } 196 | 197 | public Builder setSize(Long size) { 198 | this.size = size; 199 | return this; 200 | } 201 | 202 | public Date getLastModified() { 203 | return lastModified; 204 | } 205 | 206 | public Builder setLastModified(Date lastModified) { 207 | this.lastModified = lastModified; 208 | return this; 209 | } 210 | 211 | public FileType getType() { 212 | return type; 213 | } 214 | 215 | public Builder setType(FileType type) { 216 | this.type = type; 217 | return this; 218 | } 219 | 220 | public Builder setTypeFile() { 221 | return setType(FileType.FILE); 222 | } 223 | 224 | public Builder setTypeDir() { 225 | return setType(FileType.DIRECTORY); 226 | } 227 | 228 | public List getVersionList() { 229 | return versionList; 230 | } 231 | 232 | public Builder setVersionList(@Nullable Iterable versionList) { 233 | this.versionList = versionList == null ? null : Lists.newArrayList(versionList); 234 | return this; 235 | } 236 | 237 | public boolean isDeleted() { 238 | return deleted; 239 | } 240 | 241 | public Builder setDeleted(boolean deleted) { 242 | this.deleted = deleted; 243 | return this; 244 | } 245 | 246 | public String getHash() { 247 | return hash; 248 | } 249 | 250 | public Builder setHash(String hash) { 251 | this.hash = hash; 252 | return this; 253 | } 254 | 255 | public FileInfo build() { 256 | return new FileInfo(folder, type, path, size, lastModified, hash, versionList, deleted); 257 | } 258 | 259 | } 260 | 261 | public static void checkBlocks(FileInfo fileInfo, FileBlocks fileBlocks) { 262 | checkArgument(equal(fileBlocks.getFolder(), fileInfo.getFolder()), "file info folder not match file block folder"); 263 | checkArgument(equal(fileBlocks.getPath(), fileInfo.getPath()), "file info path does not match file block path"); 264 | checkArgument(fileInfo.isFile(), "file info must be of type 'FILE' to have blocks"); 265 | checkArgument(equal(fileBlocks.getSize(), fileInfo.getSize()), "file info size does not match file block size"); 266 | checkArgument(equal(fileBlocks.getHash(), fileInfo.getHash()), "file info hash does not match file block hash"); 267 | } 268 | 269 | } 270 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/beans/FolderInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.beans; 15 | 16 | import static com.google.common.base.MoreObjects.firstNonNull; 17 | import static com.google.common.base.Preconditions.checkNotNull; 18 | import static com.google.common.base.Strings.emptyToNull; 19 | import javax.annotation.Nullable; 20 | 21 | /** 22 | * 23 | * @author aleph 24 | */ 25 | public class FolderInfo { 26 | 27 | private final String folder; 28 | private final String label; 29 | 30 | public FolderInfo(String folder) { 31 | this(folder, null); 32 | } 33 | 34 | public FolderInfo(String folder, @Nullable String label) { 35 | checkNotNull(emptyToNull(folder)); 36 | this.folder = folder; 37 | this.label = firstNonNull(emptyToNull(label), folder); 38 | } 39 | 40 | public String getFolder() { 41 | return folder; 42 | } 43 | 44 | public String getLabel() { 45 | return label; 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return "FolderInfo{" + "folder=" + folder + ", label=" + label + '}'; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/beans/FolderStats.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.beans; 15 | 16 | import java.util.Date; 17 | import org.apache.commons.io.FileUtils; 18 | 19 | /** 20 | * 21 | * @author aleph 22 | */ 23 | public class FolderStats extends FolderInfo { 24 | 25 | private final long fileCount, dirCount, size; 26 | private final Date lastUpdate; 27 | 28 | private FolderStats(long fileCount, long dirCount, long size, Date lastUpdate, String folder, String label) { 29 | super(folder, label); 30 | this.fileCount = fileCount; 31 | this.dirCount = dirCount; 32 | this.size = size; 33 | this.lastUpdate = lastUpdate; 34 | } 35 | 36 | public long getFileCount() { 37 | return fileCount; 38 | } 39 | 40 | public long getDirCount() { 41 | return dirCount; 42 | } 43 | 44 | public long getSize() { 45 | return size; 46 | } 47 | 48 | public Date getLastUpdate() { 49 | return lastUpdate; 50 | } 51 | 52 | public long getRecordCount() { 53 | return getDirCount() + getFileCount(); 54 | } 55 | 56 | public String describeSize() { 57 | return FileUtils.byteCountToDisplaySize(getSize()); 58 | } 59 | 60 | public String dumpInfo() { 61 | return "folder " + getLabel() + " (" + getFolder() + ") file count = " + getFileCount() 62 | + " dir count = " + getDirCount() + " folder size = " + describeSize() + " last update = " + getLastUpdate(); 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "FolderStats{folder=" + getFolder() + ", fileCount=" + fileCount + ", dirCount=" + dirCount + ", size=" + size + ", lastUpdate=" + lastUpdate + '}'; 68 | } 69 | 70 | public Builder copyBuilder() { 71 | return new Builder(fileCount, dirCount, size, getFolder(), getLabel()); 72 | } 73 | 74 | public static Builder newBuilder() { 75 | return new Builder(); 76 | } 77 | 78 | public static class Builder { 79 | 80 | private long fileCount, dirCount, size; 81 | private Date lastUpdate = new Date(0); 82 | private String folder, label; 83 | 84 | private Builder() { 85 | } 86 | 87 | public Builder(long fileCount, long dirCount, long size, String folder, String label) { 88 | this.fileCount = fileCount; 89 | this.dirCount = dirCount; 90 | this.size = size; 91 | this.folder = folder; 92 | this.label = label; 93 | } 94 | 95 | public long getFileCount() { 96 | return fileCount; 97 | } 98 | 99 | public Builder setFileCount(long fileCount) { 100 | this.fileCount = fileCount; 101 | return this; 102 | } 103 | 104 | public long getDirCount() { 105 | return dirCount; 106 | } 107 | 108 | public Builder setDirCount(long dirCount) { 109 | this.dirCount = dirCount; 110 | return this; 111 | } 112 | 113 | public long getSize() { 114 | return size; 115 | } 116 | 117 | public Builder setSize(long size) { 118 | this.size = size; 119 | return this; 120 | } 121 | 122 | public Date getLastUpdate() { 123 | return lastUpdate; 124 | } 125 | 126 | public Builder setLastUpdate(Date lastUpdate) { 127 | this.lastUpdate = lastUpdate; 128 | return this; 129 | } 130 | 131 | public String getFolder() { 132 | return folder; 133 | } 134 | 135 | public Builder setFolder(String folder) { 136 | this.folder = folder; 137 | return this; 138 | } 139 | 140 | public String getLabel() { 141 | return label; 142 | } 143 | 144 | public Builder setLabel(String label) { 145 | this.label = label; 146 | return this; 147 | } 148 | 149 | public FolderStats build() { 150 | return new FolderStats(fileCount, dirCount, size, lastUpdate, folder, label); 151 | } 152 | 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/beans/IndexInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.beans; 15 | 16 | import static com.google.common.base.Strings.emptyToNull; 17 | import static com.google.common.base.Preconditions.checkNotNull; 18 | 19 | /** 20 | * 21 | * @author aleph 22 | */ 23 | public class IndexInfo extends FolderInfo { 24 | 25 | private final long indexId; 26 | private final String deviceId; 27 | private final long localSequence, maxSequence; 28 | 29 | private IndexInfo(String folder, String deviceId, long indexId, long localSequence, long maxSequence) { 30 | super(folder); 31 | checkNotNull(emptyToNull(deviceId)); 32 | this.deviceId = deviceId; 33 | this.indexId = indexId; 34 | this.localSequence = localSequence; 35 | this.maxSequence = maxSequence; 36 | } 37 | 38 | public static Builder newBuilder() { 39 | return new Builder(); 40 | } 41 | 42 | public Builder copyBuilder() { 43 | return new Builder(getFolder(), indexId, deviceId, localSequence, maxSequence); 44 | } 45 | 46 | public String getDeviceId() { 47 | return deviceId; 48 | } 49 | 50 | public long getIndexId() { 51 | return indexId; 52 | } 53 | 54 | public long getLocalSequence() { 55 | return localSequence; 56 | } 57 | 58 | public long getMaxSequence() { 59 | return maxSequence; 60 | } 61 | 62 | public double getCompleted() { 63 | return maxSequence > 0 ? ((double) localSequence) / maxSequence : 0; 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return "FolderIndexInfo{" + "indexId=" + indexId + ", folder=" + getFolder() + ", deviceId=" + deviceId + ", localSequence=" + localSequence + ", maxSequence=" + maxSequence + '}'; 69 | } 70 | 71 | public static class Builder { 72 | 73 | private long indexId; 74 | private String deviceId, folder; 75 | private long localSequence, maxSequence; 76 | 77 | private Builder() { 78 | } 79 | 80 | private Builder(String folder, long indexId, String deviceId, long localSequence, long maxSequence) { 81 | checkNotNull(emptyToNull(folder)); 82 | checkNotNull(emptyToNull(deviceId)); 83 | this.folder = folder; 84 | this.indexId = indexId; 85 | this.deviceId = deviceId; 86 | this.localSequence = localSequence; 87 | this.maxSequence = maxSequence; 88 | } 89 | 90 | public long getIndexId() { 91 | return indexId; 92 | } 93 | 94 | public String getDeviceId() { 95 | return deviceId; 96 | } 97 | 98 | public String getFolder() { 99 | return folder; 100 | } 101 | 102 | public long getLocalSequence() { 103 | return localSequence; 104 | } 105 | 106 | public long getMaxSequence() { 107 | return maxSequence; 108 | } 109 | 110 | public Builder setIndexId(long indexId) { 111 | this.indexId = indexId; 112 | return this; 113 | } 114 | 115 | public Builder setDeviceId(String deviceId) { 116 | this.deviceId = deviceId; 117 | return this; 118 | } 119 | 120 | public Builder setFolder(String folder) { 121 | this.folder = folder; 122 | return this; 123 | } 124 | 125 | public Builder setLocalSequence(long localSequence) { 126 | this.localSequence = localSequence; 127 | return this; 128 | } 129 | 130 | public Builder setMaxSequence(long maxSequence) { 131 | this.maxSequence = maxSequence; 132 | return this; 133 | } 134 | 135 | public IndexInfo build() { 136 | return new IndexInfo(folder, deviceId, indexId, localSequence, maxSequence); 137 | } 138 | 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/cache/BlockCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.cache; 15 | 16 | import it.anyplace.sync.core.configuration.ConfigurationService; 17 | import javax.annotation.Nullable; 18 | import org.slf4j.LoggerFactory; 19 | 20 | /** 21 | * 22 | * @author aleph 23 | */ 24 | public abstract class BlockCache { 25 | 26 | public static BlockCache getBlockCache(ConfigurationService configuration) { 27 | if (configuration.getCache() != null) { 28 | try { 29 | return new FileBlockCache(configuration.getCache()); 30 | } catch (Exception ex) { 31 | LoggerFactory.getLogger(BlockCache.class).warn("unable to open cache", ex); 32 | } 33 | } 34 | return new DummyBlockCache(); 35 | } 36 | 37 | /** 38 | * note: The data may be written to disk in background. Do not modify the 39 | * supplied array 40 | * 41 | * @param data 42 | * @return cache block code, or null in case of errors 43 | */ 44 | public abstract String pushBlock(byte[] data); 45 | 46 | public abstract boolean pushData(String code, byte[] data); 47 | 48 | public abstract @Nullable 49 | byte[] pullBlock(String code); 50 | 51 | public abstract @Nullable 52 | byte[] pullData(String code); 53 | 54 | public void clear(){ 55 | 56 | } 57 | 58 | private static class DummyBlockCache extends BlockCache { 59 | 60 | @Override 61 | public String pushBlock(byte[] data) { 62 | return null; 63 | } 64 | 65 | @Override 66 | public @Nullable 67 | byte[] pullBlock(String code) { 68 | return null; 69 | } 70 | 71 | @Override 72 | public boolean pushData(String code, byte[] data) { 73 | return false; 74 | } 75 | 76 | @Override 77 | public byte[] pullData(String code) { 78 | return null; 79 | } 80 | 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/cache/FileBlockCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.cache; 15 | 16 | import com.google.common.base.Function; 17 | import static com.google.common.base.Objects.equal; 18 | import static com.google.common.base.Preconditions.checkArgument; 19 | import com.google.common.collect.Iterables; 20 | import com.google.common.collect.Lists; 21 | import com.google.common.collect.Ordering; 22 | import com.google.common.hash.Hashing; 23 | import com.google.common.io.BaseEncoding; 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.util.Collections; 27 | import java.util.List; 28 | import java.util.concurrent.ExecutorService; 29 | import java.util.concurrent.Executors; 30 | import javax.annotation.Nullable; 31 | import org.apache.commons.io.FileUtils; 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | 35 | /** 36 | * 37 | * @author aleph 38 | */ 39 | public class FileBlockCache extends BlockCache { 40 | 41 | private final static ExecutorService writerThread = Executors.newSingleThreadExecutor(); 42 | 43 | private final Logger logger = LoggerFactory.getLogger(getClass()); 44 | private final File dir; 45 | private long size; 46 | private final long MAX_SIZE = 50 * 1024 * 1024; //TODO max size from config 47 | private final double PERC_TO_DELETE = 0.5; //perc of files to delete on max size cleanup 48 | 49 | public FileBlockCache(File cacheDirectory) { 50 | this.dir = cacheDirectory; 51 | if (!dir.exists()) { 52 | dir.mkdirs(); 53 | } 54 | checkArgument(dir.isDirectory() && dir.canWrite()); 55 | size = FileUtils.sizeOfDirectory(dir); 56 | runCleanup(); 57 | } 58 | 59 | @Override 60 | public String pushBlock(final byte[] data) { 61 | String code = BaseEncoding.base16().encode(Hashing.sha256().hashBytes(data).asBytes()); 62 | return pushData(code, data) ? code : null; 63 | } 64 | 65 | private void runCleanup() { 66 | if (size > MAX_SIZE) { 67 | logger.info("starting cleanup of cache directory, initial size = {}", FileUtils.byteCountToDisplaySize(size)); 68 | List files = Lists.newArrayList(dir.listFiles()); 69 | Collections.sort(files, Ordering.natural().onResultOf(new Function() { 70 | @Override 71 | public Long apply(File input) { 72 | return input.lastModified(); 73 | } 74 | })); 75 | for (File file : Iterables.limit(files, (int) (files.size() * PERC_TO_DELETE))) { 76 | logger.debug("delete file {}", file); 77 | FileUtils.deleteQuietly(file); 78 | } 79 | size = FileUtils.sizeOfDirectory(dir); 80 | logger.info("cleanup of cache directory completed, final size = {}", FileUtils.byteCountToDisplaySize(size)); 81 | } 82 | } 83 | 84 | @Override 85 | public @Nullable 86 | byte[] pullBlock(String code) { 87 | return pullFile(code, true); 88 | } 89 | 90 | private @Nullable 91 | byte[] pullFile(String code, boolean shouldCheck) { 92 | 93 | final File file = new File(dir, code); 94 | if (file.exists()) { 95 | try { 96 | byte[] data = FileUtils.readFileToByteArray(file); 97 | if (shouldCheck) { 98 | String cachedDataCode = BaseEncoding.base16().encode(Hashing.sha256().hashBytes(data).asBytes()); 99 | checkArgument(equal(code, cachedDataCode), "cached data code %s does not match code %s", cachedDataCode, code); 100 | } 101 | writerThread.submit(new Runnable() { 102 | @Override 103 | public void run() { 104 | try { 105 | FileUtils.touch(file); 106 | } catch (IOException ex) { 107 | logger.warn("unable to 'touch' file {}", file); 108 | logger.warn("unable to 'touch' file", ex); 109 | } 110 | } 111 | 112 | }); 113 | logger.debug("read block {} from cache file {}", code, file); 114 | return data; 115 | } catch (Exception ex) { 116 | logger.warn("error reading block from cache", ex); 117 | FileUtils.deleteQuietly(file); 118 | } 119 | } 120 | return null; 121 | } 122 | 123 | // @Override 124 | // public void close() { 125 | // writerThread.shutdown(); 126 | // try { 127 | // writerThread.awaitTermination(2, TimeUnit.SECONDS); 128 | // } catch (InterruptedException ex) { 129 | // logger.warn("pending threads on block cache writer executor"); 130 | // } 131 | // } 132 | @Override 133 | public boolean pushData(final String code, final byte[] data) { 134 | try { 135 | writerThread.submit(new Runnable() { 136 | @Override 137 | public void run() { 138 | File file = new File(dir, code); 139 | if (!file.exists()) { 140 | try { 141 | FileUtils.writeByteArrayToFile(file, data); 142 | logger.debug("cached block {} to file {}", code, file); 143 | size += data.length; 144 | runCleanup(); 145 | } catch (IOException ex) { 146 | logger.warn("error writing block in cache", ex); 147 | FileUtils.deleteQuietly(file); 148 | } 149 | } 150 | } 151 | }); 152 | return true; 153 | } catch (Exception ex) { 154 | logger.warn("error caching block", ex); 155 | return false; 156 | } 157 | } 158 | 159 | @Override 160 | public byte[] pullData(String code) { 161 | return pullFile(code, false); 162 | } 163 | 164 | @Override 165 | public void clear() { 166 | writerThread.submit(new Runnable() { 167 | @Override 168 | public void run() { 169 | FileUtils.deleteQuietly(dir); 170 | dir.mkdirs(); 171 | } 172 | 173 | }); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/configuration/gsonbeans/DeviceConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Davide Imbriaco . 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.configuration.gsonbeans; 15 | 16 | public class DeviceConfig { 17 | private String deviceId, name; 18 | 19 | public String getDeviceId() { 20 | return deviceId; 21 | } 22 | 23 | public void setDeviceId(String deviceId) { 24 | this.deviceId = deviceId; 25 | } 26 | 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | public void setName(String name) { 32 | this.name = name; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "DeviceConfig{" + "deviceId=" + deviceId + ", name=" + name + '}'; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/configuration/gsonbeans/DeviceConfigList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Davide Imbriaco . 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.configuration.gsonbeans; 15 | 16 | import com.google.common.collect.Lists; 17 | import java.util.List; 18 | 19 | public class DeviceConfigList { 20 | 21 | private List devices = Lists.newArrayList(); 22 | 23 | public List getDevices() { 24 | return devices; 25 | } 26 | 27 | public void setDevices(List devices) { 28 | this.devices = devices; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/configuration/gsonbeans/FolderConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.configuration.gsonbeans; 15 | 16 | /** 17 | * 18 | * @author aleph 19 | */ 20 | public class FolderConfig { 21 | 22 | private String folder, label; 23 | 24 | public String getFolder() { 25 | return folder; 26 | } 27 | 28 | public void setFolder(String folder) { 29 | this.folder = folder; 30 | } 31 | 32 | public String getLabel() { 33 | return label; 34 | } 35 | 36 | public void setLabel(String label) { 37 | this.label = label; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "FolderConfig{" + "folder=" + folder + ", label=" + label + '}'; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/configuration/gsonbeans/FolderConfigList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.configuration.gsonbeans; 15 | 16 | import com.google.common.collect.Lists; 17 | import java.util.List; 18 | 19 | /** 20 | * 21 | * @author aleph 22 | */ 23 | public class FolderConfigList { 24 | 25 | private List folders = Lists.newArrayList(); 26 | 27 | public List getFolders() { 28 | return folders; 29 | } 30 | 31 | public void setFolders(List folders) { 32 | this.folders = folders; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/events/DeviceAddressActiveEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Davide Imbriaco . 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.events; 15 | 16 | import it.anyplace.sync.core.beans.DeviceAddress; 17 | 18 | public interface DeviceAddressActiveEvent { 19 | 20 | public DeviceAddress getDeviceAddress(); 21 | } 22 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/events/DeviceAddressReceivedEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Davide Imbriaco . 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.events; 15 | 16 | import it.anyplace.sync.core.beans.DeviceAddress; 17 | import java.util.List; 18 | 19 | public interface DeviceAddressReceivedEvent { 20 | 21 | public List getDeviceAddresses(); 22 | } 23 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/interfaces/DeviceAddressRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.interfaces; 15 | 16 | import it.anyplace.sync.core.beans.DeviceAddress; 17 | import java.util.List; 18 | 19 | /** 20 | * 21 | * @author aleph 22 | */ 23 | public interface DeviceAddressRepository { 24 | 25 | public List findAllDeviceAddress(); 26 | 27 | public void updateDeviceAddress(DeviceAddress deviceAddress); 28 | } 29 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/interfaces/IndexRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.interfaces; 15 | 16 | import com.google.common.eventbus.EventBus; 17 | import it.anyplace.sync.core.beans.FileBlocks; 18 | import it.anyplace.sync.core.beans.FileInfo; 19 | import it.anyplace.sync.core.beans.FolderStats; 20 | import it.anyplace.sync.core.beans.IndexInfo; 21 | import java.util.Date; 22 | import java.util.List; 23 | import javax.annotation.Nullable; 24 | 25 | /** 26 | * 27 | * @author aleph 28 | */ 29 | public interface IndexRepository { 30 | 31 | public EventBus getEventBus(); 32 | 33 | public Sequencer getSequencer(); 34 | 35 | public void updateIndexInfo(IndexInfo indexInfo); 36 | 37 | public @Nullable 38 | IndexInfo findIndexInfoByDeviceAndFolder(String deviceId, String folder); 39 | 40 | public @Nullable 41 | FileInfo findFileInfo(String folder, String path); 42 | 43 | public @Nullable 44 | Date findFileInfoLastModified(String folder, String path); 45 | 46 | public @Nullable 47 | FileInfo findNotDeletedFileInfo(String folder, String path); 48 | 49 | public @Nullable 50 | FileBlocks findFileBlocks(String folder, String path); 51 | 52 | public void updateFileInfo(FileInfo fileInfo, @Nullable FileBlocks fileBlocks); 53 | 54 | public List findNotDeletedFilesByFolderAndParent(String folder, String parentPath); 55 | 56 | public void clearIndex(); 57 | 58 | public @Nullable 59 | FolderStats findFolderStats(String folder); 60 | 61 | public List findAllFolderStats(); 62 | 63 | public List findFileInfoBySearchTerm(String query); 64 | 65 | public long countFileInfoBySearchTerm(String query); 66 | 67 | public abstract class FolderStatsUpdatedEvent { 68 | 69 | public abstract List getFolderStats(); 70 | 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/interfaces/RelayConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.interfaces; 15 | 16 | import java.net.Socket; 17 | 18 | /** 19 | * 20 | * @author aleph 21 | */ 22 | public interface RelayConnection { 23 | 24 | public Socket getSocket(); 25 | 26 | public boolean isServerSocket(); 27 | } 28 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/interfaces/Sequencer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.interfaces; 15 | 16 | /** 17 | * 18 | * @author aleph 19 | */ 20 | public interface Sequencer { 21 | 22 | public long indexId(); 23 | 24 | public long nextSequence(); 25 | 26 | public long currentSequence(); 27 | } 28 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/interfaces/TempRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.interfaces; 15 | 16 | /** 17 | * 18 | * @author aleph 19 | */ 20 | public interface TempRepository { 21 | 22 | public String pushTempData(byte[] data); 23 | 24 | public byte[] popTempData(String key); 25 | } 26 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/logs/LogAppender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Davide Imbriaco . 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.logs; 15 | 16 | import ch.qos.logback.classic.spi.ILoggingEvent; 17 | import ch.qos.logback.core.AppenderBase; 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | import com.google.common.eventbus.EventBus; 20 | 21 | public class LogAppender extends AppenderBase { 22 | 23 | private final static EventBus globalLogEventBus = new EventBus(); 24 | 25 | @Override 26 | protected void append(final ILoggingEvent eventObject) { 27 | checkNotNull(eventObject); 28 | globalLogEventBus.post(new LogEventReceivedMessage() { 29 | @Override 30 | public LogAppender getAppender() { 31 | return LogAppender.this; 32 | } 33 | 34 | @Override 35 | public ILoggingEvent getLogEvent() { 36 | return eventObject; 37 | } 38 | }); 39 | } 40 | 41 | protected static EventBus getLogEventBus() { 42 | return globalLogEventBus; 43 | } 44 | 45 | public interface LogEventReceivedMessage { 46 | 47 | public LogAppender getAppender(); 48 | 49 | public ILoggingEvent getLogEvent(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/logs/LogService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Davide Imbriaco . 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.logs; 15 | 16 | import com.google.common.eventbus.EventBus; 17 | 18 | public class LogService { 19 | 20 | // private final EventBus eventBus=new EventBus(); 21 | 22 | 23 | public EventBus getEventBus() { 24 | return LogAppender.getLogEventBus(); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/utils/BlockUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.utils; 15 | 16 | import com.google.common.base.Function; 17 | import com.google.common.base.Joiner; 18 | import com.google.common.collect.Iterables; 19 | import com.google.common.hash.Hashing; 20 | import com.google.common.io.BaseEncoding; 21 | import it.anyplace.sync.core.beans.BlockInfo; 22 | import java.util.List; 23 | 24 | /** 25 | * 26 | * @author aleph 27 | */ 28 | public class BlockUtils { 29 | 30 | public static String hashBlocks(List blocks) { 31 | return BaseEncoding.base16().encode(Hashing.sha256().hashBytes(Joiner.on(",").join(Iterables.transform(blocks, new Function() { 32 | @Override 33 | public String apply(BlockInfo input) { 34 | return input.getHash(); 35 | } 36 | })).getBytes()).asBytes()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/utils/ExecutorUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.utils; 15 | 16 | import java.util.concurrent.ExecutorService; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | /** 20 | * 21 | * @author aleph 22 | */ 23 | public class ExecutorUtils { 24 | 25 | public static void awaitTerminationSafe(ExecutorService executorService) { 26 | try { 27 | executorService.awaitTermination(2, TimeUnit.SECONDS); 28 | } catch (InterruptedException ex) { 29 | } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/utils/FileInfoOrdering.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Davide Imbriaco . 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.utils; 15 | 16 | import com.google.common.base.Function; 17 | import com.google.common.base.Functions; 18 | import com.google.common.base.Predicate; 19 | import com.google.common.collect.ComparisonChain; 20 | import it.anyplace.sync.core.beans.FileInfo; 21 | import java.util.Comparator; 22 | 23 | public class FileInfoOrdering { 24 | 25 | private final static Function isParentFunction = Functions.forPredicate(new Predicate() { 26 | @Override 27 | public boolean apply(FileInfo fileInfo) { 28 | return PathUtils.isParent(fileInfo.getPath()); 29 | } 30 | }); 31 | 32 | public static final Comparator ALPHA_ASC_DIR_FIRST = new Comparator() { 33 | @Override 34 | public int compare(FileInfo a, FileInfo b) { 35 | return ComparisonChain.start() 36 | .compareTrueFirst(isParentFunction.apply(a), isParentFunction.apply(b)) 37 | .compare(a.isDirectory() ? 1 : 2, b.isDirectory() ? 1 : 2) 38 | .compare(a.getPath(), b.getPath()) 39 | .result(); 40 | } 41 | 42 | }; 43 | 44 | public static final Comparator LAST_MOD_DESC = new Comparator() { 45 | @Override 46 | public int compare(FileInfo a, FileInfo b) { 47 | return ComparisonChain.start() 48 | .compareTrueFirst(isParentFunction.apply(a), isParentFunction.apply(b)) 49 | .compare(b.getLastModified(), a.getLastModified()) 50 | .compare(a.getPath(), b.getPath()) 51 | .result(); 52 | } 53 | 54 | }; 55 | 56 | } 57 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.utils; 15 | 16 | import it.anyplace.sync.core.configuration.ConfigurationService; 17 | import java.io.File; 18 | import java.util.UUID; 19 | 20 | public class FileUtils { 21 | 22 | public static File createTempFile(ConfigurationService configuration) { 23 | File tempFile = new File(configuration.getTemp(), UUID.randomUUID().toString()); 24 | tempFile.deleteOnExit(); 25 | return tempFile; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /a-sync-core/src/main/java/it/anyplace/sync/core/utils/PathUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.core.utils; 15 | 16 | import static com.google.common.base.Objects.equal; 17 | import com.google.common.base.Strings; 18 | import org.apache.commons.io.FilenameUtils; 19 | import static com.google.common.base.Preconditions.checkArgument; 20 | 21 | /** 22 | * 23 | * @author aleph 24 | */ 25 | public class PathUtils { 26 | 27 | public static final String ROOT_PATH = "", PATH_SEPARATOR = "/", PARENT_PATH = ".."; 28 | 29 | public static String normalizePath(String path) { 30 | return FilenameUtils.normalizeNoEndSeparator(path, true).replaceFirst("^" + PATH_SEPARATOR, ""); 31 | } 32 | 33 | public static boolean isRoot(String path) { 34 | return Strings.isNullOrEmpty(path); 35 | } 36 | 37 | public static boolean isParent(String path) { 38 | return equal(path, PARENT_PATH); 39 | } 40 | 41 | public static String getParentPath(String path) { 42 | checkArgument(!isRoot(path), "cannot get parent of root path"); 43 | return normalizePath(path + PATH_SEPARATOR + PARENT_PATH); 44 | } 45 | 46 | public static String getFileName(String path) { 47 | return FilenameUtils.getName(path); 48 | } 49 | 50 | public static String buildPath(String dir, String file) { 51 | return normalizePath(dir + PATH_SEPARATOR + file); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /a-sync-core/src/main/resources/default.properties: -------------------------------------------------------------------------------- 1 | 2 | keystore.required=true 3 | 4 | #repository.h2.dboptions=;TRACE_LEVEL_FILE=0;TRACE_LEVEL_SYSTEM_OUT=0;DB_CLOSE_DELAY=10 5 | #android optimization 6 | #repository.h2.dboptions=;TRACE_LEVEL_FILE=0;TRACE_LEVEL_SYSTEM_OUT=0;DB_CLOSE_DELAY=10;FILE_LOCK=FS;PAGE_SIZE=1024;CACHE_SIZE=8192 7 | #repository.h2.dboptions=;TRACE_LEVEL_FILE=0;TRACE_LEVEL_SYSTEM_OUT=3;FILE_LOCK=FS;PAGE_SIZE=1024;CACHE_SIZE=8192;MULTI_THREADED=1 8 | repository.h2.dboptions=;TRACE_LEVEL_FILE=0;TRACE_LEVEL_SYSTEM_OUT=0;FILE_LOCK=FS;PAGE_SIZE=1024;CACHE_SIZE=8192; 9 | 10 | discoveryserver=discovery-v4-2.syncthing.net,discovery-v4-3.syncthing.net,discovery-v4-4.syncthing.net,discovery-v6-2.syncthing.net,discovery-v6-3.syncthing.net,discovery-v6-4.syncthing.net -------------------------------------------------------------------------------- /a-sync-discovery/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | it.anyplace.sync 6 | a-sync-parent 7 | 1.3 8 | ../a-sync-parent 9 | 10 | a-sync-discovery 11 | jar 12 | 13 | 14 | 15 | ${project.groupId} 16 | a-sync-core 17 | ${project.version} 18 | 19 | 20 | ${project.groupId} 21 | a-sync-repository 22 | ${project.version} 23 | 24 | 25 | -------------------------------------------------------------------------------- /a-sync-discovery/src/main/java/it/anyplace/sync/discovery/DeviceAddressSupplier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.discovery; 15 | 16 | import com.google.common.base.Function; 17 | import com.google.common.collect.Ordering; 18 | import com.google.common.eventbus.Subscribe; 19 | import it.anyplace.sync.core.beans.DeviceAddress; 20 | import java.io.Closeable; 21 | import java.util.PriorityQueue; 22 | import java.util.Queue; 23 | import javax.annotation.Nullable; 24 | import java.util.Iterator; 25 | import static com.google.common.base.Preconditions.checkArgument; 26 | import static com.google.common.base.Preconditions.checkNotNull; 27 | 28 | /** 29 | * 30 | * @author aleph 31 | */ 32 | public class DeviceAddressSupplier implements Closeable, Iterable { 33 | 34 | private final DiscoveryHandler discoveryHandler; 35 | private final Queue deviceAddressQeue = new PriorityQueue(11, Ordering.natural().onResultOf(new Function() { 36 | @Override 37 | public Integer apply(DeviceAddress deviceAddress) { 38 | return deviceAddress.getScore(); 39 | } 40 | })); 41 | private final Object queueLock = new Object(); 42 | private final Object discoveryHandlerListener = new Object() { 43 | @Subscribe 44 | public void handleNewDeviceAddressAcquiredEvent(DiscoveryHandler.DeviceAddressUpdateEvent event) { 45 | if (event.getDeviceAddress().isWorking()) { 46 | synchronized (queueLock) { 47 | deviceAddressQeue.add(event.getDeviceAddress()); 48 | queueLock.notify(); 49 | } 50 | } 51 | } 52 | }; 53 | 54 | protected DeviceAddressSupplier(DiscoveryHandler discoveryHandler) { 55 | checkNotNull(discoveryHandler); 56 | this.discoveryHandler = discoveryHandler; 57 | synchronized (queueLock) { 58 | discoveryHandler.getEventBus().register(discoveryHandlerListener); 59 | deviceAddressQeue.addAll(discoveryHandler.getAllWorkingDeviceAddresses());// note: slight risk of duplicate address loading 60 | } 61 | } 62 | 63 | public @Nullable 64 | DeviceAddress getDeviceAddress() { 65 | synchronized (queueLock) { 66 | return deviceAddressQeue.poll(); 67 | } 68 | } 69 | 70 | public @Nullable 71 | DeviceAddress getDeviceAddressOrWait(long timeout) throws InterruptedException { 72 | synchronized (queueLock) { 73 | if (deviceAddressQeue.isEmpty()) { 74 | queueLock.wait(timeout); 75 | } 76 | return getDeviceAddress(); 77 | } 78 | } 79 | 80 | public @Nullable 81 | DeviceAddress getDeviceAddressOrWait() throws InterruptedException { 82 | return getDeviceAddressOrWait(5000); 83 | } 84 | 85 | @Override 86 | public void close() { 87 | discoveryHandler.getEventBus().unregister(discoveryHandlerListener); 88 | } 89 | 90 | @Override 91 | public Iterator iterator() { 92 | return new Iterator() { 93 | 94 | private Boolean hasNext = null; 95 | private DeviceAddress next; 96 | 97 | @Override 98 | public boolean hasNext() { 99 | if (hasNext == null) { 100 | try { 101 | next = getDeviceAddressOrWait(); 102 | } catch (InterruptedException ex) { 103 | } 104 | hasNext = next != null; 105 | } 106 | return hasNext; 107 | } 108 | 109 | @Override 110 | public DeviceAddress next() { 111 | checkArgument(hasNext()); 112 | DeviceAddress res = next; 113 | hasNext = null; 114 | next = null; 115 | return res; 116 | } 117 | 118 | @Override 119 | public void remove() { 120 | throw new UnsupportedOperationException(); 121 | } 122 | }; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /a-sync-discovery/src/main/java/it/anyplace/sync/discovery/DiscoveryHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.discovery; 15 | 16 | import com.google.common.base.Predicate; 17 | import com.google.common.collect.Iterables; 18 | import com.google.common.collect.Lists; 19 | import com.google.common.collect.Maps; 20 | import com.google.common.collect.Sets; 21 | import com.google.common.eventbus.EventBus; 22 | import com.google.common.eventbus.Subscribe; 23 | import it.anyplace.sync.core.configuration.ConfigurationService; 24 | import it.anyplace.sync.core.beans.DeviceAddress; 25 | import it.anyplace.sync.core.events.DeviceAddressReceivedEvent; 26 | import it.anyplace.sync.core.interfaces.DeviceAddressRepository; 27 | import it.anyplace.sync.core.utils.ExecutorUtils; 28 | import it.anyplace.sync.discovery.protocol.gd.GlobalDiscoveryHandler; 29 | import it.anyplace.sync.discovery.protocol.ld.LocalDiscorveryHandler; 30 | import it.anyplace.sync.discovery.utils.AddressRanker; 31 | import java.io.Closeable; 32 | import java.util.Collections; 33 | import java.util.List; 34 | import java.util.Map; 35 | import java.util.Set; 36 | import java.util.concurrent.ExecutorService; 37 | import java.util.concurrent.Executors; 38 | import org.apache.commons.lang3.tuple.Pair; 39 | import org.slf4j.Logger; 40 | import org.slf4j.LoggerFactory; 41 | 42 | /** 43 | * 44 | * @author aleph 45 | */ 46 | public class DiscoveryHandler implements Closeable { 47 | 48 | private final Logger logger = LoggerFactory.getLogger(getClass()); 49 | private final ConfigurationService configuration; 50 | private final GlobalDiscoveryHandler globalDiscoveryHandler; 51 | private final LocalDiscorveryHandler localDiscorveryHandler; 52 | private final DeviceAddressRepository deviceAddressRepository; 53 | private final EventBus eventBus = new EventBus(); 54 | private final ExecutorService executorService = Executors.newCachedThreadPool(); 55 | private final Map, DeviceAddress> deviceAddressMap = Collections.synchronizedMap(Maps., DeviceAddress>newHashMap()); 56 | private boolean isClosed = false; 57 | 58 | public DiscoveryHandler(ConfigurationService configuration, DeviceAddressRepository deviceAddressRepository) { 59 | logger.info("init"); 60 | this.configuration = configuration; 61 | this.deviceAddressRepository = deviceAddressRepository; 62 | localDiscorveryHandler = new LocalDiscorveryHandler(configuration); 63 | globalDiscoveryHandler = new GlobalDiscoveryHandler(configuration); 64 | localDiscorveryHandler.getEventBus().register(new Object() { 65 | @Subscribe 66 | public void handleMessageReceivedEvent(LocalDiscorveryHandler.MessageReceivedEvent event) { 67 | logger.info("received device address list from local discovery"); 68 | processDeviceAddressBg(event.getDeviceAddresses()); 69 | } 70 | }); 71 | } 72 | 73 | boolean shouldLoadFromDb = true, shouldLoadFromGlobal = true, shouldStartLocalDiscovery = true; 74 | 75 | private void updateAddressesBg() { 76 | if (shouldLoadFromDb) { 77 | shouldLoadFromDb = false; 78 | executorService.submit(new Runnable() { 79 | @Override 80 | public void run() { 81 | try { 82 | List list = DiscoveryHandler.this.deviceAddressRepository.findAllDeviceAddress(); 83 | logger.info("received device address list from database"); 84 | processDeviceAddressBg(list); 85 | } catch (Exception ex) { 86 | logger.error("error loading device addresses from db", ex); 87 | } 88 | } 89 | }); 90 | } 91 | if (shouldStartLocalDiscovery) { 92 | shouldStartLocalDiscovery = false; 93 | localDiscorveryHandler.startListener(); 94 | localDiscorveryHandler.sendAnnounceMessage(); 95 | } 96 | if (shouldLoadFromGlobal) { 97 | shouldLoadFromGlobal = false; //TODO timeout for reload 98 | executorService.submit(new Runnable() { 99 | @Override 100 | public void run() { 101 | try { 102 | for (String deviceId : DiscoveryHandler.this.configuration.getPeerIds()) { 103 | List list = globalDiscoveryHandler.query(deviceId); 104 | logger.info("received device address list from global discovery"); 105 | processDeviceAddressBg(list); 106 | } 107 | } catch (Exception ex) { 108 | logger.error("error loading device addresses from db", ex); 109 | } 110 | } 111 | 112 | }); 113 | } 114 | } 115 | 116 | private void processDeviceAddressBg(final Iterable deviceAddresses) { 117 | if (isClosed) { 118 | logger.debug("discarding device addresses, discovery handler already closed"); 119 | } else { 120 | executorService.submit(new Runnable() { 121 | @Override 122 | public void run() { 123 | try { 124 | logger.info("processing device address list"); 125 | List list = Lists.newArrayList(deviceAddresses); 126 | final Set peers = Sets.newHashSet(configuration.getPeerIds()); 127 | Iterables.removeIf(list, new Predicate() { //do not process address already processed 128 | @Override 129 | public boolean apply(DeviceAddress deviceAddress) { 130 | return !peers.contains(deviceAddress.getDeviceId()) || deviceAddressMap.containsKey(Pair.of(deviceAddress.getDeviceId(), deviceAddress.getAddress())); 131 | } 132 | }); 133 | list = AddressRanker.testAndRank(list); 134 | for (DeviceAddress deviceAddress : list) { 135 | putDeviceAddress(deviceAddress); 136 | } 137 | } catch (Exception ex) { 138 | logger.error("error processing device addresses", ex); 139 | } 140 | } 141 | }); 142 | } 143 | } 144 | 145 | private void putDeviceAddress(final DeviceAddress deviceAddress) { 146 | logger.info("acquired device address = {}", deviceAddress); 147 | deviceAddressMap.put(Pair.of(deviceAddress.getDeviceId(), deviceAddress.getAddress()), deviceAddress); 148 | deviceAddressRepository.updateDeviceAddress(deviceAddress); 149 | eventBus.post(new DeviceAddressUpdateEvent() { 150 | @Override 151 | public DeviceAddress getDeviceAddress() { 152 | return deviceAddress; 153 | } 154 | }); 155 | } 156 | 157 | public EventBus getEventBus() { 158 | return eventBus; 159 | } 160 | 161 | public DeviceAddressSupplier newDeviceAddressSupplier() { 162 | DeviceAddressSupplier deviceAddressSupplier = new DeviceAddressSupplier(this); 163 | updateAddressesBg(); 164 | return deviceAddressSupplier; 165 | } 166 | 167 | public List getAllWorkingDeviceAddresses() { 168 | return Lists.newArrayList(Iterables.filter(deviceAddressMap.values(), new Predicate() { 169 | @Override 170 | public boolean apply(DeviceAddress deviceAddress) { 171 | return deviceAddress.isWorking(); 172 | } 173 | })); 174 | } 175 | 176 | @Override 177 | public void close() { 178 | if (!isClosed) { 179 | isClosed = true; 180 | if (localDiscorveryHandler != null) { 181 | localDiscorveryHandler.close(); 182 | } 183 | if (globalDiscoveryHandler != null) { 184 | globalDiscoveryHandler.close(); 185 | } 186 | executorService.shutdown(); 187 | ExecutorUtils.awaitTerminationSafe(executorService); 188 | } 189 | } 190 | 191 | public abstract class DeviceAddressUpdateEvent implements DeviceAddressReceivedEvent { 192 | 193 | public abstract DeviceAddress getDeviceAddress(); 194 | 195 | @Override 196 | public List getDeviceAddresses() { 197 | return Collections.singletonList(getDeviceAddress()); 198 | } 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /a-sync-discovery/src/main/java/it/anyplace/sync/discovery/protocol/gd/GlobalDiscoveryHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.discovery.protocol.gd; 15 | 16 | import com.google.common.base.Function; 17 | import static com.google.common.base.MoreObjects.firstNonNull; 18 | import com.google.common.cache.CacheBuilder; 19 | import com.google.common.cache.CacheLoader; 20 | import com.google.common.cache.LoadingCache; 21 | import com.google.gson.Gson; 22 | import java.util.Collections; 23 | import java.util.List; 24 | import org.apache.http.HttpResponse; 25 | import org.apache.http.HttpStatus; 26 | import org.apache.http.client.methods.HttpGet; 27 | import org.apache.http.impl.client.HttpClients; 28 | import org.apache.http.util.EntityUtils; 29 | import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 30 | import org.apache.http.conn.ssl.TrustSelfSignedStrategy; 31 | import com.google.common.collect.Iterables; 32 | import com.google.common.collect.Lists; 33 | import it.anyplace.sync.core.configuration.ConfigurationService; 34 | import it.anyplace.sync.core.beans.DeviceAddress; 35 | import it.anyplace.sync.discovery.utils.AddressRanker; 36 | import java.io.Closeable; 37 | import java.io.IOException; 38 | import java.util.concurrent.ExecutionException; 39 | import java.util.concurrent.TimeUnit; 40 | import javax.annotation.Nullable; 41 | import org.apache.http.client.ClientProtocolException; 42 | import org.apache.http.client.HttpClient; 43 | import org.apache.http.client.ResponseHandler; 44 | import org.apache.http.conn.ssl.SSLContextBuilder; 45 | import org.slf4j.Logger; 46 | import org.slf4j.LoggerFactory; 47 | 48 | /** 49 | * 50 | * @author aleph 51 | */ 52 | public class GlobalDiscoveryHandler implements Closeable { 53 | 54 | private final Logger logger = LoggerFactory.getLogger(getClass()); 55 | private final ConfigurationService configuration; 56 | private final Gson gson = new Gson(); 57 | private final LoadingCache> cache = CacheBuilder.newBuilder() 58 | .expireAfterAccess(30, TimeUnit.MINUTES) 59 | .refreshAfterWrite(10, TimeUnit.MINUTES) 60 | .build(new CacheLoader>() { 61 | @Override 62 | public List load(String deviceId) throws Exception { 63 | return doQuery(deviceId); 64 | } 65 | }); 66 | private List serverList = null; 67 | 68 | // private final ExecutorService executorService = Executors.newCachedThreadPool(); 69 | public GlobalDiscoveryHandler(ConfigurationService configuration) { 70 | this.configuration = configuration; 71 | } 72 | 73 | public List query(final String deviceId) { 74 | try { 75 | return cache.get(deviceId); 76 | } catch (ExecutionException ex) { 77 | throw new RuntimeException(ex); 78 | } 79 | } 80 | 81 | private List doQuery(final String deviceId) { 82 | synchronized (this) { 83 | if (serverList == null) { 84 | logger.debug("ranking discovery server addresses"); 85 | List list = AddressRanker.testAndRank(Lists.transform(configuration.getDiscoveryServers(), new Function() { 86 | @Override 87 | public DeviceAddress apply(String input) { 88 | return new DeviceAddress(input, "tcp://" + input + ":443"); 89 | } 90 | })); 91 | logger.info("discovery server addresses = \n\n{}\n", AddressRanker.dumpAddressRanking(list)); 92 | serverList = Lists.newArrayList(Lists.transform(list, new Function() { 93 | @Override 94 | public String apply(DeviceAddress input) { 95 | return input.getDeviceId(); 96 | } 97 | })); 98 | } 99 | } 100 | for (String server : serverList) { 101 | List list = doQuery(server, deviceId); 102 | if (list != null) { 103 | return list; 104 | } 105 | } 106 | return Collections.emptyList(); 107 | } 108 | 109 | /** 110 | * 111 | * @param server 112 | * @param deviceId 113 | * @return null on error, empty list on 'not found', address list otherwise 114 | */ 115 | private @Nullable 116 | List doQuery(String server, final String deviceId) { 117 | try { 118 | logger.trace("querying server {} for device id {}", server, deviceId); 119 | HttpClient httpClient = HttpClients.custom() 120 | .setSSLSocketFactory(new SSLConnectionSocketFactory(new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(), SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)) 121 | .build(); 122 | HttpGet httpGet = new HttpGet("https://" + server + "/v2/?device=" + deviceId); 123 | return httpClient.execute(httpGet, new ResponseHandler>() { 124 | @Override 125 | public List handleResponse(HttpResponse response) throws ClientProtocolException, IOException { 126 | switch (response.getStatusLine().getStatusCode()) { 127 | case HttpStatus.SC_NOT_FOUND: 128 | logger.debug("device not found: {}", deviceId); 129 | return Collections.emptyList(); 130 | case HttpStatus.SC_OK: 131 | AnnouncementMessageBean announcementMessageBean = gson.fromJson(EntityUtils.toString(response.getEntity()), AnnouncementMessageBean.class); 132 | List list = Lists.newArrayList(Iterables.transform(firstNonNull(announcementMessageBean.getAddresses(), Collections.emptyList()), new Function() { 133 | @Override 134 | public DeviceAddress apply(String address) { 135 | return new DeviceAddress(deviceId, address); 136 | } 137 | })); 138 | logger.debug("found address list = {}", list); 139 | return list; 140 | default: 141 | throw new IOException("http error " + response.getStatusLine()); 142 | } 143 | } 144 | }); 145 | } catch (Exception ex) { 146 | logger.warn("error in global discovery for device = " + deviceId, ex); 147 | } 148 | return null; 149 | } 150 | // 151 | // private List doQuery(final String deviceId) { 152 | // List serverList = configuration.getDiscoveryServers(); 153 | // List>> futures = Lists.newArrayList(); 154 | // for (final String server : serverList) { 155 | // futures.add(executorService.submit(new Callable>() { 156 | // @Override 157 | // public List call() throws Exception { 158 | // return doQuery(server, deviceId); 159 | // } 160 | // })); 161 | // } 162 | // while (!futures.isEmpty()) { 163 | // Iterator>> iterator = futures.iterator(); 164 | // while (iterator.hasNext()) { 165 | // Future> future = iterator.next(); 166 | // if (future.isDone()) { 167 | // iterator.remove(); 168 | // try { 169 | // List list = future.get(); 170 | // if (!list.isEmpty()) { 171 | // return list; 172 | // } 173 | // } catch (InterruptedException | ExecutionException ex) { 174 | // throw new RuntimeException(ex); 175 | // } 176 | // } 177 | // } 178 | // } 179 | // return Collections.emptyList(); 180 | // } 181 | 182 | @Override 183 | public void close() { 184 | // executorService.shutdown(); 185 | // try { 186 | // executorService.awaitTermination(2, TimeUnit.SECONDS); 187 | // } catch (InterruptedException ex) { 188 | // } 189 | } 190 | 191 | public static class AnnouncementMessageBean { 192 | 193 | private List addresses; 194 | 195 | public List getAddresses() { 196 | return addresses; 197 | } 198 | 199 | public void setAddresses(List addresses) { 200 | this.addresses = addresses; 201 | } 202 | 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /a-sync-discovery/src/main/java/it/anyplace/sync/discovery/utils/AddressRanker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.discovery.utils; 15 | 16 | import com.google.common.base.Function; 17 | import com.google.common.base.Joiner; 18 | import static com.google.common.base.Objects.equal; 19 | import com.google.common.base.Stopwatch; 20 | import com.google.common.cache.CacheBuilder; 21 | import com.google.common.cache.CacheLoader; 22 | import com.google.common.cache.LoadingCache; 23 | import com.google.common.collect.ImmutableMap; 24 | import com.google.common.collect.Lists; 25 | import com.google.common.collect.Ordering; 26 | import com.google.common.collect.Sets; 27 | import it.anyplace.sync.core.beans.DeviceAddress; 28 | import it.anyplace.sync.core.beans.DeviceAddress.AddressType; 29 | import java.io.Closeable; 30 | import java.io.IOException; 31 | import java.net.InetAddress; 32 | import java.net.InetSocketAddress; 33 | import java.net.Socket; 34 | import java.net.SocketAddress; 35 | import java.util.Collections; 36 | import java.util.List; 37 | import java.util.Map; 38 | import java.util.Set; 39 | import java.util.concurrent.Callable; 40 | import java.util.concurrent.ExecutionException; 41 | import java.util.concurrent.ExecutorService; 42 | import java.util.concurrent.Executors; 43 | import java.util.concurrent.Future; 44 | import java.util.concurrent.TimeUnit; 45 | import java.util.concurrent.TimeoutException; 46 | import javax.annotation.Nullable; 47 | import org.apache.commons.lang3.tuple.Pair; 48 | import org.slf4j.Logger; 49 | import org.slf4j.LoggerFactory; 50 | 51 | /** 52 | * 53 | * @author aleph 54 | */ 55 | public class AddressRanker implements Closeable { 56 | 57 | private final Logger logger = LoggerFactory.getLogger(getClass()); 58 | private final ExecutorService executorService = Executors.newCachedThreadPool(); 59 | private final List sourceAddresses, targetAddresses; 60 | 61 | private AddressRanker(Iterable sourceAddresses) { 62 | this.sourceAddresses = Collections.unmodifiableList(Lists.newArrayList(sourceAddresses)); 63 | this.targetAddresses = Collections.synchronizedList(Lists.newArrayList()); 64 | } 65 | 66 | private List preprocessDeviceAddresses(List list) { 67 | Set res = Sets.newLinkedHashSet(); 68 | for (DeviceAddress deviceAddress : list) { 69 | logger.debug("preprocess address = {}", deviceAddress.getAddress()); 70 | if (equal(deviceAddress.getType(), AddressType.RELAY) && deviceAddress.containsUriParamValue("httpUrl")) { 71 | String httpUrl = deviceAddress.getUriParam("httpUrl"); 72 | DeviceAddress httpRelayAddress = deviceAddress.copyBuilder() 73 | .setAddress("relay-" + httpUrl).build(); 74 | logger.debug("extracted http relay address = {}", httpRelayAddress.getAddress()); 75 | res.add(httpRelayAddress); 76 | } 77 | res.add(deviceAddress); 78 | } 79 | return Lists.newArrayList(res); 80 | } 81 | 82 | private void testAndRankAndWait() throws InterruptedException { 83 | logger.trace("testing and ranking peer addresses"); 84 | List> futures = Lists.newArrayList(); 85 | for (final DeviceAddress deviceAddress : preprocessDeviceAddresses(sourceAddresses)) { 86 | futures.add(executorService.submit(new Callable() { 87 | 88 | @Override 89 | public DeviceAddress call() { 90 | return testAndRank(deviceAddress); 91 | } 92 | })); 93 | } 94 | for (Future future : futures) { 95 | try { 96 | DeviceAddress deviceAddress = future.get(TCP_CONNECTION_TIMEOUT * 2, TimeUnit.MILLISECONDS); 97 | if (deviceAddress != null) { 98 | targetAddresses.add(deviceAddress); 99 | } 100 | } catch (ExecutionException ex) { 101 | throw new RuntimeException(ex); 102 | } catch (TimeoutException ex) { 103 | logger.warn("test address timeout : {}", ex.toString()); 104 | } 105 | } 106 | Collections.sort(targetAddresses, Ordering.natural().onResultOf(new Function() { 107 | @Override 108 | public Comparable apply(DeviceAddress a) { 109 | return a.getScore(); 110 | } 111 | })); 112 | } 113 | 114 | public static String dumpAddressRanking(List list) { 115 | return Joiner.on("\n").join(Lists.transform(list, new Function() { 116 | @Override 117 | public String apply(DeviceAddress a) { 118 | return "\t" + a.getDeviceId() + "\t" + a.getAddress(); 119 | } 120 | })); 121 | } 122 | 123 | public String dumpAddressRanking() { 124 | return dumpAddressRanking(targetAddresses); 125 | } 126 | 127 | private static final int TCP_CONNECTION_TIMEOUT = 1000; 128 | private static final Map BASE_SCORE_MAP = ImmutableMap.builder() 129 | .put(AddressType.TCP, 0) 130 | .put(AddressType.RELAY, TCP_CONNECTION_TIMEOUT * 2) 131 | .put(AddressType.HTTP_RELAY, TCP_CONNECTION_TIMEOUT * TCP_CONNECTION_TIMEOUT * 2) 132 | .put(AddressType.HTTPS_RELAY, TCP_CONNECTION_TIMEOUT * TCP_CONNECTION_TIMEOUT * 2) 133 | .build(); 134 | private static final Set ACCEPTED_ADDRESS_TYPES = BASE_SCORE_MAP.keySet(); 135 | 136 | private @Nullable 137 | DeviceAddress testAndRank(DeviceAddress deviceAddress) { 138 | if (!ACCEPTED_ADDRESS_TYPES.contains(deviceAddress.getType())) { 139 | logger.trace("dropping unsupported address = {}", deviceAddress); 140 | return null; 141 | } 142 | try { 143 | int baseScore = BASE_SCORE_MAP.get(deviceAddress.getType()); 144 | int ping = testTcpConnection(deviceAddress.getSocketAddress()); 145 | if (ping < 0) { 146 | logger.trace("dropping unreacheable address = {}", deviceAddress); 147 | return null; 148 | } else { 149 | return deviceAddress.copyBuilder().setScore(ping + baseScore).build(); 150 | } 151 | } catch (Exception ex) { 152 | logger.warn("error testing device address = {}, discarding address", deviceAddress); 153 | logger.warn("error testing device address, discarding address", ex); 154 | return null; 155 | } 156 | } 157 | 158 | public List getTargetAddresses() { 159 | return targetAddresses; 160 | } 161 | 162 | public static List testAndRank(Iterable list) { 163 | try (AddressRanker addressRanker = new AddressRanker(list)) { 164 | addressRanker.testAndRankAndWait(); 165 | return addressRanker.getTargetAddresses(); 166 | } catch (InterruptedException ex) { 167 | throw new RuntimeException(ex); 168 | } 169 | } 170 | 171 | // private boolean hasCompleted() { 172 | // return count == sourceAddresses.size(); 173 | // } 174 | @Override 175 | public void close() { 176 | executorService.shutdown(); 177 | try { 178 | executorService.awaitTermination(2, TimeUnit.SECONDS); 179 | } catch (InterruptedException ex) { 180 | } 181 | } 182 | 183 | private final LoadingCache, Integer> socketAddressScoreCache = CacheBuilder.newBuilder() 184 | .expireAfterAccess(10, TimeUnit.SECONDS) 185 | .build(new CacheLoader, Integer>() { 186 | @Override 187 | public Integer load(Pair key) throws Exception { 188 | return doTestTcpConnection(new InetSocketAddress(InetAddress.getByName(key.getLeft()), key.getRight())); 189 | } 190 | }); 191 | 192 | private int doTestTcpConnection(SocketAddress socketAddress) { 193 | logger.debug("test tcp connection to address = {}", socketAddress); 194 | Stopwatch stopwatch = Stopwatch.createStarted(); 195 | try (Socket socket = new Socket()) { 196 | socket.setSoTimeout(TCP_CONNECTION_TIMEOUT); 197 | socket.connect(socketAddress, TCP_CONNECTION_TIMEOUT); 198 | } catch (IOException ex) { 199 | logger.debug("address unreacheable = {} ({})", socketAddress, ex.toString()); 200 | logger.trace("address unreacheable", ex); 201 | return -1; 202 | } 203 | int time = (int) stopwatch.elapsed(TimeUnit.MILLISECONDS); 204 | logger.debug("tcp connection to address = {} is ok, time = {} ms", socketAddress, time); 205 | return time; 206 | } 207 | 208 | private int testTcpConnection(InetSocketAddress socketAddress) { 209 | return socketAddressScoreCache.getUnchecked(Pair.of(socketAddress.getAddress().getHostAddress(), socketAddress.getPort())); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /a-sync-discovery/src/main/resources/localDiscoveryProtos.proto: -------------------------------------------------------------------------------- 1 | package it.anyplace.sync.discovery.protocol.ld.protos; 2 | 3 | message Announce { 4 | optional bytes id = 1; 5 | repeated string addresses = 2; 6 | optional int64 instance_id = 3; 7 | } 8 | -------------------------------------------------------------------------------- /a-sync-http-relay-server/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd `dirname $0`/.. 4 | ./deploy.sh || exit 1 5 | rsync -crvP a-sync-http-relay-server/target/a-sync-http-relay-server-1.0-SNAPSHOT-executable.jar aleph@kimbox: 6 | 7 | -------------------------------------------------------------------------------- /a-sync-http-relay-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | it.anyplace.sync 6 | a-sync-parent 7 | 1.3 8 | ../a-sync-parent 9 | 10 | a-sync-http-relay-server 11 | jar 12 | 13 | 14 | 15 | ${project.groupId} 16 | a-sync-core 17 | ${project.version} 18 | 19 | 20 | ${project.groupId} 21 | a-sync-http-relay 22 | ${project.version} 23 | 24 | 25 | 26 | 27 | 28 | 29 | maven-compiler-plugin 30 | 3.6.0 31 | 32 | 1.7 33 | 1.7 34 | 35 | 36 | 37 | org.apache.maven.plugins 38 | maven-shade-plugin 39 | 2.4.1 40 | 41 | 42 | 43 | 44 | package 45 | 46 | shade 47 | 48 | 49 | 50 | 51 | it.anyplace.sync.relay.server.Main 52 | 53 | 54 | 55 | 56 | *:* 57 | 58 | META-INF/*.SF 59 | META-INF/*.DSA 60 | META-INF/*.RSA 61 | 62 | 63 | 64 | true 65 | executable 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /a-sync-http-relay-server/src/main/java/it/anyplace/sync/relay/server/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.relay.server; 15 | 16 | import static com.google.common.base.MoreObjects.firstNonNull; 17 | import static com.google.common.base.Strings.emptyToNull; 18 | import it.anyplace.sync.core.beans.DeviceAddress; 19 | import it.anyplace.sync.httprelay.server.HttpRelayServer; 20 | import org.apache.commons.cli.CommandLine; 21 | import org.apache.commons.cli.CommandLineParser; 22 | import org.apache.commons.cli.DefaultParser; 23 | import org.apache.commons.cli.HelpFormatter; 24 | import org.apache.commons.cli.Options; 25 | import org.apache.commons.cli.ParseException; 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | 29 | /** 30 | * 31 | * @author aleph 32 | */ 33 | public class Main { 34 | 35 | private final static Logger logger = LoggerFactory.getLogger(Main.class); 36 | 37 | public static void main(String[] args) throws ParseException, Exception { 38 | Options options = new Options(); 39 | options.addOption("r", "relay-server", true, "set relay server to serve for"); 40 | options.addOption("p", "port", true, "set http server port"); 41 | options.addOption("h", "help", false, "print help"); 42 | CommandLineParser parser = new DefaultParser(); 43 | CommandLine cmd = parser.parse(options, args); 44 | 45 | if (cmd.hasOption("h")) { 46 | HelpFormatter formatter = new HelpFormatter(); 47 | formatter.printHelp("s-client", options); 48 | return; 49 | } 50 | int port = cmd.hasOption("p") ? Integer.parseInt(cmd.getOptionValue("p")) : 22080; 51 | String relayServer = firstNonNull(emptyToNull(cmd.getOptionValue("r")), "relay://localhost"); 52 | logger.info("starting http relay server :{} for relay server {}", port, relayServer); 53 | HttpRelayServer httpRelayServer = new HttpRelayServer(DeviceAddress.newBuilder().setDeviceId("relay").setAddress(relayServer).build().getSocketAddress()); 54 | httpRelayServer.start(port); 55 | httpRelayServer.join(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /a-sync-http-relay-server/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /a-sync-http-relay/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | it.anyplace.sync 6 | a-sync-parent 7 | 1.3 8 | ../a-sync-parent 9 | 10 | a-sync-http-relay 11 | jar 12 | 13 | 14 | ${project.groupId} 15 | a-sync-relay 16 | ${project.version} 17 | 18 | 19 | org.eclipse.jetty 20 | jetty-server 21 | 8.2.0.v20160908 22 | 23 | 24 | -------------------------------------------------------------------------------- /a-sync-http-relay/src/main/java/it/anyplace/sync/httprelay/client/HttpRelayClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.httprelay.client; 15 | 16 | import it.anyplace.sync.core.beans.DeviceAddress; 17 | import it.anyplace.sync.core.beans.DeviceAddress.AddressType; 18 | import java.util.EnumSet; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | import static com.google.common.base.Preconditions.checkArgument; 22 | import static com.google.common.base.Preconditions.checkNotNull; 23 | import it.anyplace.sync.core.configuration.ConfigurationService; 24 | import static com.google.common.base.Preconditions.checkArgument; 25 | import static com.google.common.base.Preconditions.checkNotNull; 26 | 27 | /** 28 | * 29 | * @author aleph 30 | */ 31 | public class HttpRelayClient { 32 | 33 | private final Logger logger = LoggerFactory.getLogger(getClass()); 34 | private final ConfigurationService configuration; 35 | 36 | public HttpRelayClient(ConfigurationService configuration) { 37 | this.configuration = configuration; 38 | } 39 | 40 | public HttpRelayConnection openRelayConnection(DeviceAddress deviceAddress) throws Exception { 41 | checkNotNull(deviceAddress); 42 | checkArgument(EnumSet.of(AddressType.HTTP_RELAY, AddressType.HTTPS_RELAY).contains(deviceAddress.getType())); 43 | String httpRelayServerUrl = deviceAddress.getAddress().replaceFirst("^relay-", ""); 44 | String deviceId = deviceAddress.getDeviceId(); 45 | logger.info("open http relay connection, relay url = {}, target device id = {}", httpRelayServerUrl, deviceId); 46 | return new HttpRelayConnection(httpRelayServerUrl, deviceId); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /a-sync-http-relay/src/main/java/it/anyplace/sync/httprelay/server/HttpRelayServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.httprelay.server; 15 | 16 | import com.google.common.base.Strings; 17 | import com.google.common.collect.Maps; 18 | import com.google.common.eventbus.Subscribe; 19 | import com.google.protobuf.ByteString; 20 | import it.anyplace.sync.core.configuration.ConfigurationService; 21 | import it.anyplace.sync.httprelay.server.RelaySessionConnection.ConnectionClosedEvent; 22 | import it.anyplace.sync.httprelay.protos.HttpRelayProtos; 23 | import java.io.IOException; 24 | import java.net.InetSocketAddress; 25 | import java.util.Map; 26 | import javax.servlet.ServletException; 27 | import javax.servlet.http.HttpServletRequest; 28 | import javax.servlet.http.HttpServletResponse; 29 | import org.eclipse.jetty.server.Request; 30 | import org.eclipse.jetty.server.Server; 31 | import org.eclipse.jetty.server.handler.AbstractHandler; 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | import static com.google.common.base.Strings.emptyToNull; 35 | import it.anyplace.sync.client.protocol.rp.RelayClient; 36 | import it.anyplace.sync.client.protocol.rp.beans.SessionInvitation; 37 | import it.anyplace.sync.core.security.KeystoreHandler; 38 | import it.anyplace.sync.core.interfaces.RelayConnection; 39 | import java.io.File; 40 | import static com.google.common.base.Preconditions.checkNotNull; 41 | import java.io.Closeable; 42 | import org.apache.commons.io.IOUtils; 43 | 44 | /** 45 | * 46 | * @author aleph 47 | */ 48 | public class HttpRelayServer implements Closeable { 49 | 50 | private final Logger logger = LoggerFactory.getLogger(getClass()); 51 | private final InetSocketAddress relayServerAddress; 52 | private Server server; 53 | private final static long MAX_WAIT_FOR_DATA_SECS = 30; 54 | private ConfigurationService configuration; 55 | private final KeystoreHandler keystoreHandler; 56 | 57 | public HttpRelayServer(InetSocketAddress relayServerAddress) { 58 | this.relayServerAddress = relayServerAddress; 59 | try { 60 | this.configuration = ConfigurationService.newLoader().loadFrom(new File(System.getProperty("user.home"), ".config/a-sync-http-relay.properties")); 61 | } catch (Exception ex) { 62 | logger.warn("error loading config", ex); 63 | this.configuration = ConfigurationService.newLoader().load(); 64 | } 65 | keystoreHandler = KeystoreHandler.newLoader().loadAndStore(configuration); 66 | } 67 | 68 | private final Map relayConnectionsBySessionId = Maps.newConcurrentMap(); 69 | 70 | private RelaySessionConnection openConnection(String deviceId) throws Exception { 71 | RelayClient relayClient = new RelayClient(configuration); 72 | SessionInvitation sessionInvitation = relayClient.getSessionInvitation(relayServerAddress, deviceId); 73 | RelayConnection relayConnection = relayClient.openConnectionSessionMode(sessionInvitation); 74 | final RelaySessionConnection relaySessionConnection = new RelaySessionConnection(relayConnection); 75 | relayConnectionsBySessionId.put(relaySessionConnection.getSessionId(), relaySessionConnection); 76 | relaySessionConnection.getEventBus().register(new Object() { 77 | @Subscribe 78 | public void handleConnectionClosedEvent(ConnectionClosedEvent event) { 79 | relayConnectionsBySessionId.remove(relaySessionConnection.getSessionId()); 80 | } 81 | }); 82 | relaySessionConnection.connect(); 83 | return relaySessionConnection; 84 | } 85 | 86 | public void start(int port) throws Exception { 87 | server = new Server(port); 88 | // if (soapSsl) { 89 | // SslContextFactory sslContextFactory = new SslContextFactory(); 90 | // sslContextFactory.setKeyStorePath(Main.class.getResource("/keystore.jks").toExternalForm()); 91 | // sslContextFactory.setKeyStorePassword("cjstorepass"); 92 | // sslContextFactory.setKeyManagerPassword("cjrestkeypass"); 93 | // SslSocketConnector connector = new SslSocketConnector(sslContextFactory); 94 | // connector.setPort(serverPort); 95 | // server.setConnectors(new Connector[]{connector}); 96 | // } else { 97 | // SocketConnector connector = new SocketConnector(); 98 | // connector.setPort(port); 99 | // server.setConnectors(new Connector[]{connector}); 100 | // } 101 | 102 | server.setHandler(new AbstractHandler() { 103 | 104 | @Override 105 | public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { 106 | logger.trace("handling requenst"); 107 | HttpRelayProtos.HttpRelayServerMessage serverMessage; 108 | try { 109 | HttpRelayProtos.HttpRelayPeerMessage peerMessage = HttpRelayProtos.HttpRelayPeerMessage.parseFrom(request.getInputStream()); 110 | logger.debug("handle peer message type = {} session id = {} sequence = {}", peerMessage.getMessageType(), peerMessage.getSessionId(), peerMessage.getSequence()); 111 | serverMessage = handleMessage(peerMessage); 112 | } catch (Exception ex) { 113 | logger.error("error", ex); 114 | serverMessage = HttpRelayProtos.HttpRelayServerMessage.newBuilder() 115 | .setMessageType(HttpRelayProtos.HttpRelayServerMessageType.ERROR) 116 | .setData(ByteString.copyFromUtf8("error : " + ex.toString())) 117 | .build(); 118 | } 119 | logger.debug("send server response message type = {} session id = {} sequence = {}", serverMessage.getMessageType(), serverMessage.getSessionId(), serverMessage.getSequence()); 120 | try { 121 | serverMessage.writeTo(response.getOutputStream()); 122 | response.getOutputStream().flush(); 123 | } catch (Exception ex) { 124 | logger.error("error", ex); 125 | } 126 | } 127 | }); 128 | server.start(); 129 | logger.info("http relay server READY on port {}", port); 130 | } 131 | 132 | private HttpRelayProtos.HttpRelayServerMessage handleMessage(HttpRelayProtos.HttpRelayPeerMessage message) throws Exception { 133 | switch (message.getMessageType()) { 134 | case CONNECT: { 135 | String deviceId = message.getDeviceId(); 136 | checkNotNull(emptyToNull(deviceId)); 137 | RelaySessionConnection connection = openConnection(deviceId); 138 | return HttpRelayProtos.HttpRelayServerMessage.newBuilder() 139 | .setMessageType(HttpRelayProtos.HttpRelayServerMessageType.PEER_CONNECTED) 140 | .setSessionId(connection.getSessionId()) 141 | .setIsServerSocket(connection.isServerSocket()) 142 | .build(); 143 | } 144 | case PEER_CLOSING: { 145 | RelaySessionConnection connection = requireConnectionBySessionId(message.getSessionId()); 146 | connection.close(); 147 | return HttpRelayProtos.HttpRelayServerMessage.newBuilder() 148 | .setMessageType(HttpRelayProtos.HttpRelayServerMessageType.SERVER_CLOSING) 149 | .build(); 150 | } 151 | case PEER_TO_RELAY: { 152 | RelaySessionConnection connection = requireConnectionBySessionId(message.getSessionId()); 153 | connection.sendData(message); 154 | return HttpRelayProtos.HttpRelayServerMessage.newBuilder() 155 | .setMessageType(HttpRelayProtos.HttpRelayServerMessageType.DATA_ACCEPTED) 156 | .build(); 157 | } 158 | case WAIT_FOR_DATA: { 159 | RelaySessionConnection connection = requireConnectionBySessionId(message.getSessionId()); 160 | HttpRelayProtos.HttpRelayServerMessage response = connection.waitForDataAndGet(MAX_WAIT_FOR_DATA_SECS * 1000); 161 | return response; 162 | } 163 | } 164 | throw new IllegalArgumentException("unsupported message type = " + message.getMessageType()); 165 | } 166 | 167 | private RelaySessionConnection requireConnectionBySessionId(String sessionId) { 168 | checkNotNull(Strings.emptyToNull(sessionId)); 169 | RelaySessionConnection connection = relayConnectionsBySessionId.get(sessionId); 170 | checkNotNull(connection, "connection not found for sessionId = %s", sessionId); 171 | return connection; 172 | } 173 | 174 | public void join() throws InterruptedException { 175 | server.join(); 176 | } 177 | 178 | @Override 179 | public void close() { 180 | try { 181 | server.stop(); 182 | } catch (Exception ex) { 183 | logger.warn("error stopping server", ex); 184 | } 185 | IOUtils.closeQuietly(configuration); 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /a-sync-http-relay/src/main/java/it/anyplace/sync/httprelay/server/RelaySessionConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.httprelay.server; 15 | 16 | import static com.google.common.base.Objects.equal; 17 | import com.google.common.eventbus.EventBus; 18 | import java.io.Closeable; 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.io.OutputStream; 22 | import java.net.Socket; 23 | import java.util.UUID; 24 | import java.util.concurrent.ExecutorService; 25 | import java.util.concurrent.Executors; 26 | import java.util.concurrent.TimeUnit; 27 | import org.apache.commons.io.IOUtils; 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | import com.google.protobuf.ByteString; 31 | import it.anyplace.sync.httprelay.protos.HttpRelayProtos; 32 | import it.anyplace.sync.httprelay.protos.HttpRelayProtos.HttpRelayPeerMessage; 33 | import it.anyplace.sync.httprelay.protos.HttpRelayProtos.HttpRelayServerMessage; 34 | import java.io.File; 35 | import java.io.FileOutputStream; 36 | import org.apache.commons.io.FileUtils; 37 | import static com.google.common.base.Preconditions.checkArgument; 38 | import it.anyplace.sync.core.interfaces.RelayConnection; 39 | 40 | /** 41 | * 42 | * @author aleph 43 | */ 44 | public class RelaySessionConnection implements Closeable { 45 | 46 | private final Logger logger = LoggerFactory.getLogger(getClass()); 47 | private final String sessionId; 48 | private final EventBus eventBus = new EventBus(); 49 | private Socket socket; 50 | private InputStream inputStream; 51 | private OutputStream outputStream; 52 | private long peerToRelaySequence = 0, relayToPeerSequence = 0; 53 | private final ExecutorService readerExecutorService = Executors.newSingleThreadExecutor(), processorService = Executors.newSingleThreadExecutor(); 54 | private File tempFile; 55 | private final RelayConnection relayConnection; 56 | 57 | public RelaySessionConnection(RelayConnection relayConnection) { 58 | this.relayConnection = relayConnection; 59 | this.sessionId = UUID.randomUUID().toString(); 60 | } 61 | 62 | public long getPeerToRelaySequence() { 63 | return peerToRelaySequence; 64 | } 65 | 66 | public long getRelayToPeerSequence() { 67 | return relayToPeerSequence; 68 | } 69 | 70 | public void sendData(HttpRelayPeerMessage message) throws IOException { 71 | synchronized (outputStream) { 72 | checkArgument(equal(message.getMessageType(), HttpRelayProtos.HttpRelayPeerMessageType.PEER_TO_RELAY)); 73 | checkArgument(equal(message.getSessionId(), sessionId)); 74 | checkArgument(message.getSequence() == peerToRelaySequence + 1); 75 | if (message.getData() != null && !message.getData().isEmpty()) { 76 | try { 77 | logger.debug("sending {} bytes of data from peer to relay", message.getData().size()); 78 | message.getData().writeTo(outputStream); 79 | peerToRelaySequence = message.getSequence(); 80 | } catch (IOException ex) { 81 | close(); 82 | throw ex; 83 | } 84 | } 85 | } 86 | } 87 | 88 | private File getTempFile() { 89 | synchronized (inputStream) { 90 | if (tempFile == null) { 91 | tempFile = createTempFile(); 92 | } 93 | return tempFile; 94 | } 95 | } 96 | 97 | private File createTempFile() { 98 | try { 99 | File file = File.createTempFile("http-relay-" + sessionId, null); 100 | file.deleteOnExit(); 101 | return file; 102 | } catch (IOException ex) { 103 | throw new RuntimeException(ex); 104 | } 105 | } 106 | 107 | private byte[] popTempFile() { 108 | try { 109 | File newFile; 110 | synchronized (inputStream) { 111 | if (!hasData()) { 112 | return new byte[0]; 113 | } 114 | newFile = createTempFile(); 115 | getTempFile().renameTo(newFile); 116 | tempFile = null; 117 | } 118 | byte[] data = FileUtils.readFileToByteArray(newFile); 119 | FileUtils.deleteQuietly(newFile); 120 | logger.debug("returning {} bytes of data from relay to peer", data.length); 121 | return data; 122 | } catch (IOException ex) { 123 | throw new RuntimeException(ex); 124 | } 125 | } 126 | 127 | public HttpRelayServerMessage getData() { 128 | checkArgument(!isClosed()); 129 | byte[] data = popTempFile(); 130 | return HttpRelayServerMessage.newBuilder() 131 | .setData(ByteString.copyFrom(data)) 132 | .setMessageType(HttpRelayProtos.HttpRelayServerMessageType.RELAY_TO_PEER) 133 | .setSequence(++relayToPeerSequence) 134 | .build(); 135 | } 136 | 137 | private boolean isClosed = false; 138 | 139 | public boolean isClosed() { 140 | return isClosed; 141 | } 142 | 143 | public boolean hasData() { 144 | synchronized (inputStream) { 145 | return getTempFile().exists() && getTempFile().length() > 0; 146 | } 147 | } 148 | 149 | public HttpRelayServerMessage waitForDataAndGet(long timeout) { 150 | synchronized (inputStream) { 151 | if (!isClosed() && !hasData()) { 152 | try { 153 | inputStream.wait(timeout); 154 | } catch (InterruptedException ex) { 155 | } 156 | } 157 | } 158 | if (isClosed()) { 159 | return HttpRelayServerMessage.newBuilder().setMessageType(HttpRelayProtos.HttpRelayServerMessageType.SERVER_CLOSING).build(); 160 | } else { 161 | return getData(); 162 | } 163 | } 164 | 165 | public void connect() throws IOException { 166 | socket = relayConnection.getSocket(); 167 | inputStream = socket.getInputStream(); 168 | outputStream = socket.getOutputStream(); 169 | readerExecutorService.submit(new Runnable() { 170 | 171 | private final byte[] buffer = new byte[1024 * 10]; //10k 172 | 173 | @Override 174 | public void run() { 175 | while (!Thread.interrupted() && !isClosed()) { 176 | try { 177 | int count = inputStream.read(buffer); 178 | if (count < 0) { 179 | closeBg(); 180 | return; 181 | } 182 | if (count == 0) { 183 | continue; 184 | } 185 | logger.info("received {} bytes from relay for session {}", count, sessionId); 186 | synchronized (inputStream) { 187 | try (OutputStream out = new FileOutputStream(getTempFile(), true)) { 188 | out.write(buffer, 0, count); 189 | } 190 | inputStream.notifyAll(); 191 | } 192 | processorService.submit(new Runnable() { 193 | @Override 194 | public void run() { 195 | eventBus.post(DataReceivedEvent.INSTANCE); 196 | } 197 | }); 198 | } catch (IOException ex) { 199 | if (isClosed()) { 200 | return; 201 | } 202 | logger.error("error reading data", ex); 203 | closeBg(); 204 | return; 205 | } 206 | } 207 | } 208 | 209 | private void closeBg() { 210 | 211 | new Thread() { 212 | @Override 213 | public void run() { 214 | close(); 215 | } 216 | }.start(); 217 | } 218 | }); 219 | } 220 | 221 | public String getSessionId() { 222 | return sessionId; 223 | } 224 | 225 | public EventBus getEventBus() { 226 | return eventBus; 227 | } 228 | 229 | @Override 230 | public void close() { 231 | if (!isClosed()) { 232 | isClosed = true; 233 | logger.info("closing connection for session = {}", sessionId); 234 | readerExecutorService.shutdown(); 235 | processorService.shutdown(); 236 | if (inputStream != null) { 237 | IOUtils.closeQuietly(inputStream); 238 | synchronized (inputStream) { 239 | inputStream.notifyAll(); 240 | } 241 | inputStream = null; 242 | } 243 | if (outputStream != null) { 244 | IOUtils.closeQuietly(outputStream); 245 | outputStream = null; 246 | } 247 | if (socket != null) { 248 | IOUtils.closeQuietly(socket); 249 | socket = null; 250 | } 251 | try { 252 | readerExecutorService.awaitTermination(2, TimeUnit.SECONDS); 253 | } catch (InterruptedException ex) { 254 | } 255 | try { 256 | processorService.awaitTermination(2, TimeUnit.SECONDS); 257 | } catch (InterruptedException ex) { 258 | } 259 | eventBus.post(ConnectionClosedEvent.INSTANCE); 260 | } 261 | } 262 | 263 | public boolean isServerSocket() { 264 | return relayConnection.isServerSocket(); 265 | } 266 | 267 | public enum DataReceivedEvent { 268 | INSTANCE; 269 | } 270 | 271 | public enum ConnectionClosedEvent { 272 | INSTANCE; 273 | } 274 | 275 | } 276 | -------------------------------------------------------------------------------- /a-sync-http-relay/src/main/resources/httpRelayProtos.proto: -------------------------------------------------------------------------------- 1 | package it.anyplace.sync.httprelay.protos; 2 | 3 | message HttpRelayPeerMessage{ 4 | optional HttpRelayPeerMessageType message_type = 1; 5 | optional string session_id = 2; 6 | optional string device_id = 3; 7 | optional int64 sequence = 4; 8 | optional bytes data = 5; 9 | } 10 | 11 | message HttpRelayServerMessage{ 12 | optional HttpRelayServerMessageType message_type = 1; 13 | optional string session_id = 2; 14 | optional bool is_server_socket = 3; 15 | optional int64 sequence = 4; 16 | optional bytes data = 5; 17 | } 18 | 19 | enum HttpRelayPeerMessageType { 20 | CONNECT = 0; 21 | PEER_TO_RELAY = 1; 22 | WAIT_FOR_DATA = 2; 23 | PEER_CLOSING = 3; 24 | } 25 | 26 | enum HttpRelayServerMessageType { 27 | PEER_CONNECTED = 0; 28 | DATA_ACCEPTED = 1; 29 | RELAY_TO_PEER = 2; 30 | SERVER_CLOSING = 3; 31 | ERROR = 4; 32 | } 33 | -------------------------------------------------------------------------------- /a-sync-parent/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | it.anyplace.sync 5 | a-sync-parent 6 | 1.3 7 | pom 8 | a-sync-parent 9 | a-sync-parent 10 | https://github.com/davide-imbriaco/a-sync 11 | 12 | 13 | UTF-8 14 | 1.7 15 | 1.7 16 | 17 | 18 | 19 | 20 | Mozilla Public License Version 2.0 21 | http://mozilla.org/MPL/2.0/ 22 | repo 23 | 24 | 25 | 26 | 27 | scm:git:https://github.com/davide-imbriaco/a-sync.git 28 | scm:git:git@github.com:davide-imbriaco/a-sync.git 29 | https://github.com/davide-imbriaco/a-sync 30 | 31 | 32 | 33 | 34 | davide.imbriaco 35 | Davide Imbriaco 36 | davide.imbriaco@gmail.com 37 | anyplace.it 38 | http://anyplace.it 39 | 40 | owner 41 | developer 42 | 43 | Europe/Rome 44 | 45 | 46 | 47 | 48 | 49 | org.apache.commons 50 | commons-lang3 51 | 3.4 52 | 53 | 54 | commons-io 55 | commons-io 56 | 2.4 57 | 58 | 59 | org.slf4j 60 | slf4j-api 61 | 1.7.13 62 | 63 | 64 | ch.qos.logback 65 | logback-classic 66 | 1.1.3 67 | 68 | 69 | commons-cli 70 | commons-cli 71 | 1.3.1 72 | 73 | 74 | com.google.code.gson 75 | gson 76 | 2.5 77 | 78 | 79 | com.google.guava 80 | guava 81 | 19.0 82 | 83 | 84 | com.google.code.findbugs 85 | jsr305 86 | 3.0.0 87 | 88 | 89 | commons-beanutils 90 | commons-beanutils 91 | 1.9.2 92 | 93 | 94 | org.apache.httpcomponents 95 | httpclient 96 | 4.3.5 97 | 98 | 99 | com.google.protobuf 100 | protobuf-java 101 | 2.6.1 102 | 103 | 104 | org.bouncycastle 105 | bcmail-jdk15on 106 | 1.55 107 | 108 | 109 | org.bouncycastle 110 | bcpkix-jdk15on 111 | 1.55 112 | 113 | 114 | net.jpountz.lz4 115 | lz4 116 | 1.3.0 117 | 118 | 119 | org.mortbay.jetty.alpn 120 | alpn-boot 121 | 7.1.3.v20150130 122 | provided 123 | 124 | 125 | 126 | 127 | 128 | 129 | org.apache.maven.plugins 130 | maven-jar-plugin 131 | 3.0.2 132 | 133 | 134 | 135 | true 136 | true 137 | 138 | 139 | 140 | 141 | 142 | org.apache.maven.plugins 143 | maven-source-plugin 144 | 3.0.1 145 | 146 | 147 | attach-sources 148 | 149 | jar 150 | 151 | 152 | 153 | 154 | 155 | org.apache.maven.plugins 156 | maven-javadoc-plugin 157 | 2.10.4 158 | 159 | 160 | attach-javadocs 161 | 162 | jar 163 | 164 | 165 | 166 | 167 | 168 | org.apache.maven.plugins 169 | maven-gpg-plugin 170 | 1.6 171 | 172 | 173 | sign-artifacts 174 | verify 175 | 176 | sign 177 | 178 | 179 | 180 | 181 | 182 | org.sonatype.plugins 183 | nexus-staging-maven-plugin 184 | 1.6.7 185 | true 186 | 187 | ossrh 188 | https://oss.sonatype.org/ 189 | true 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /a-sync-relay/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | it.anyplace.sync 6 | a-sync-parent 7 | 1.3 8 | ../a-sync-parent 9 | 10 | a-sync-relay 11 | jar 12 | 13 | 14 | ${project.groupId} 15 | a-sync-core 16 | ${project.version} 17 | 18 | 19 | -------------------------------------------------------------------------------- /a-sync-relay/src/main/java/it/anyplace/sync/client/protocol/rp/beans/SessionInvitation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.client.protocol.rp.beans; 15 | 16 | import static com.google.common.base.Preconditions.checkNotNull; 17 | import com.google.common.base.Strings; 18 | import java.net.InetAddress; 19 | import java.net.InetSocketAddress; 20 | 21 | /** 22 | * 23 | * @author aleph 24 | */ 25 | public class SessionInvitation { 26 | 27 | private final String from, key; 28 | private final InetAddress address; 29 | private final int port; 30 | private final boolean isServerSocket; 31 | 32 | private SessionInvitation(String from, String key, InetAddress address, int port, boolean isServerSocket) { 33 | checkNotNull(Strings.emptyToNull(from)); 34 | checkNotNull(Strings.emptyToNull(key)); 35 | checkNotNull(address); 36 | this.from = from; 37 | this.key = key; 38 | this.address = address; 39 | this.port = port; 40 | this.isServerSocket = isServerSocket; 41 | } 42 | 43 | public String getFrom() { 44 | return from; 45 | } 46 | 47 | public String getKey() { 48 | return key; 49 | } 50 | 51 | public InetAddress getAddress() { 52 | return address; 53 | } 54 | 55 | public int getPort() { 56 | return port; 57 | } 58 | 59 | public boolean isServerSocket() { 60 | return isServerSocket; 61 | } 62 | 63 | public static Builder newBuilder() { 64 | return new Builder(); 65 | } 66 | 67 | public InetSocketAddress getSocketAddress() { 68 | return new InetSocketAddress(getAddress(), getPort()); 69 | } 70 | 71 | public static class Builder { 72 | 73 | private String from, key; 74 | private InetAddress address; 75 | private int port; 76 | private boolean isServerSocket; 77 | 78 | private Builder() { 79 | 80 | } 81 | 82 | public String getFrom() { 83 | return from; 84 | } 85 | 86 | public String getKey() { 87 | return key; 88 | } 89 | 90 | public InetAddress getAddress() { 91 | return address; 92 | } 93 | 94 | public int getPort() { 95 | return port; 96 | } 97 | 98 | public boolean isServerSocket() { 99 | return isServerSocket; 100 | } 101 | 102 | public Builder setFrom(String from) { 103 | this.from = from; 104 | return this; 105 | } 106 | 107 | public Builder setKey(String key) { 108 | this.key = key; 109 | return this; 110 | } 111 | 112 | public Builder setAddress(InetAddress address) { 113 | this.address = address; 114 | return this; 115 | } 116 | 117 | public Builder setPort(int port) { 118 | this.port = port; 119 | return this; 120 | } 121 | 122 | public Builder setServerSocket(boolean isServerSocket) { 123 | this.isServerSocket = isServerSocket; 124 | return this; 125 | } 126 | 127 | public SessionInvitation build() { 128 | return new SessionInvitation(from, key, address, port, isServerSocket); 129 | } 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /a-sync-repository/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | it.anyplace.sync 6 | a-sync-parent 7 | 1.3 8 | ../a-sync-parent 9 | 10 | a-sync-repository 11 | jar 12 | 13 | 14 | ${project.groupId} 15 | a-sync-core 16 | ${project.version} 17 | 18 | 19 | com.h2database 20 | h2 21 | 1.4.193 22 | 23 | 24 | com.zaxxer 25 | HikariCP-java7 26 | 2.4.9 27 | 28 | 29 | -------------------------------------------------------------------------------- /a-sync-repository/src/main/resources/indexSerializationProtos.proto: -------------------------------------------------------------------------------- 1 | package it.anyplace.sync.core.repo.protos; 2 | 3 | message Index { 4 | optional int64 version = 1; 5 | optional int64 sequence = 2; 6 | optional int64 last_update = 5; 7 | repeated PeerIndexInfo peers = 3; 8 | repeated FileInfo files = 4; 9 | repeated FolderInfo folders = 6; 10 | } 11 | 12 | message PeerIndexInfo { 13 | optional string device_id = 3; 14 | optional string folder = 4; 15 | optional uint64 id = 1; 16 | optional int64 sequence = 2; 17 | } 18 | 19 | message FileInfo { 20 | optional string folder = 12; 21 | optional string name = 1; 22 | optional FileInfoType type = 2; 23 | optional int64 size = 3; 24 | optional uint32 permissions = 4; 25 | optional int64 modified_s = 5; 26 | optional int32 modified_ns = 11; 27 | optional bool deleted = 6; 28 | optional bool invalid = 7; 29 | optional bool no_permissions = 8; 30 | optional Vector version = 9; 31 | optional int64 sequence = 10; 32 | repeated BlockInfo Blocks = 16; 33 | } 34 | 35 | message FolderInfo { 36 | optional string folder = 1; 37 | optional string label = 2; 38 | } 39 | 40 | enum FileInfoType { 41 | FILE = 0; 42 | DIRECTORY = 1; 43 | SYMLINK_FILE = 2; 44 | SYMLINK_DIRECTORY = 3; 45 | SYMLINK_UNKNOWN = 4; 46 | } 47 | 48 | message Blocks { 49 | repeated BlockInfo blocks = 1; 50 | } 51 | 52 | message BlockInfo { 53 | optional int64 offset = 1; 54 | optional int32 size = 2; 55 | optional bytes hash = 3; 56 | } 57 | 58 | message Vector { 59 | repeated Counter counters = 1; 60 | } 61 | 62 | message Counter { 63 | optional uint64 id = 1; 64 | optional uint64 value = 2; 65 | } 66 | 67 | -------------------------------------------------------------------------------- /a-sync-webclient/nbactions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | run 5 | 6 | jar 7 | 8 | 9 | process-classes 10 | org.codehaus.mojo:exec-maven-plugin:1.2.1:exec 11 | 12 | 13 | -classpath %classpath it.anyplace.sync.webclient.Main 14 | java 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /a-sync-webclient/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | it.anyplace.sync 6 | a-sync-parent 7 | 1.3 8 | ../a-sync-parent 9 | 10 | a-sync-webclient 11 | jar 12 | 13 | 14 | 15 | ${project.groupId} 16 | a-sync-client 17 | ${project.version} 18 | 19 | 20 | org.eclipse.jetty 21 | jetty-http 22 | 8.1.22.v20160922 23 | 24 | 25 | 26 | 27 | 28 | 29 | maven-compiler-plugin 30 | 3.6.0 31 | 32 | 1.7 33 | 1.7 34 | 35 | 36 | 37 | org.apache.maven.plugins 38 | maven-shade-plugin 39 | 2.4.1 40 | 41 | 42 | 43 | 44 | package 45 | 46 | shade 47 | 48 | 49 | 50 | 51 | it.anyplace.sync.webclient.Main 52 | 53 | 54 | 55 | 56 | *:* 57 | 58 | META-INF/*.SF 59 | META-INF/*.DSA 60 | META-INF/*.RSA 61 | 62 | 63 | 64 | true 65 | executable 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /a-sync-webclient/src/main/java/it/anyplace/sync/webclient/HttpService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package it.anyplace.sync.webclient; 6 | 7 | import it.anyplace.sync.client.SyncthingClient; 8 | import it.anyplace.sync.core.configuration.ConfigurationService; 9 | import java.io.Closeable; 10 | import java.io.IOException; 11 | import java.util.logging.Level; 12 | import javax.servlet.ServletException; 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import org.apache.commons.io.IOUtils; 16 | import org.eclipse.jetty.server.Request; 17 | import org.eclipse.jetty.server.Server; 18 | import org.eclipse.jetty.server.handler.AbstractHandler; 19 | import org.eclipse.jetty.server.handler.ContextHandler; 20 | import org.eclipse.jetty.server.handler.HandlerList; 21 | import org.eclipse.jetty.server.handler.ResourceHandler; 22 | import org.eclipse.jetty.util.resource.Resource; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | 26 | public class HttpService implements Closeable { 27 | 28 | private final Logger logger = LoggerFactory.getLogger(getClass()); 29 | 30 | private final ConfigurationService configuration; 31 | private final SyncthingClient syncthingClient; 32 | private final static int PORT = 8385; 33 | private final Server server; 34 | 35 | public HttpService(ConfigurationService configuration) { 36 | this.configuration = configuration; 37 | this.syncthingClient = new SyncthingClient(configuration); 38 | this.server = new Server(PORT); 39 | 40 | HandlerList handlerList = new HandlerList(); 41 | { 42 | ContextHandler contextHandler = new ContextHandler("/api"); 43 | contextHandler.setHandler(new AbstractHandler() { 44 | @Override 45 | public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { 46 | logger.info("ws!!"); 47 | 48 | new Thread() { 49 | @Override 50 | public void run() { 51 | try { 52 | Thread.sleep(200); 53 | } catch (InterruptedException ex) { 54 | } 55 | close(); 56 | } 57 | }.start(); 58 | 59 | response.setContentType("application/json"); 60 | response.getWriter().write("{success:true}"); 61 | } 62 | }); 63 | handlerList.addHandler(contextHandler); 64 | } 65 | { 66 | ResourceHandler resourceHandler = new ResourceHandler(); 67 | resourceHandler.setBaseResource(Resource.newClassPathResource("/web")); 68 | ContextHandler contextHandler = new ContextHandler("/web"); 69 | contextHandler.setHandler(resourceHandler); 70 | handlerList.addHandler(contextHandler); 71 | } 72 | server.setHandler(handlerList); 73 | } 74 | 75 | @Override 76 | public void close() { 77 | try { 78 | if (server.isRunning()) { 79 | server.stop(); 80 | } 81 | } catch (Exception ex) { 82 | logger.error("error stopping jetty server", ex); 83 | } 84 | IOUtils.closeQuietly(syncthingClient); 85 | } 86 | 87 | public void start() throws Exception { 88 | logger.info("starting jetty server"); 89 | server.start(); 90 | } 91 | 92 | public void join() throws Exception { 93 | server.join(); 94 | } 95 | 96 | public int getPort() { 97 | return PORT; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /a-sync-webclient/src/main/java/it/anyplace/sync/webclient/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Davide Imbriaco 3 | * 4 | * This Java file is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package it.anyplace.sync.webclient; 15 | 16 | import it.anyplace.sync.core.configuration.ConfigurationService; 17 | import it.anyplace.sync.core.security.KeystoreHandler; 18 | import java.awt.Desktop; 19 | import java.io.File; 20 | import java.net.URI; 21 | import org.apache.commons.cli.CommandLine; 22 | import org.apache.commons.cli.CommandLineParser; 23 | import org.apache.commons.cli.DefaultParser; 24 | import org.apache.commons.cli.HelpFormatter; 25 | import org.apache.commons.cli.Options; 26 | import org.apache.commons.io.FileUtils; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | /** 31 | * 32 | * @author aleph 33 | */ 34 | public class Main { 35 | 36 | private final static Logger logger = LoggerFactory.getLogger(Main.class); 37 | 38 | public static void main(String[] args) throws Exception { 39 | Options options = new Options(); 40 | options.addOption("C", "set-config", true, "set config file for s-client"); 41 | options.addOption("h", "help", false, "print help"); 42 | CommandLineParser parser = new DefaultParser(); 43 | CommandLine cmd = parser.parse(options, args); 44 | 45 | if (cmd.hasOption("h")) { 46 | HelpFormatter formatter = new HelpFormatter(); 47 | formatter.printHelp("s-client", options); 48 | return; 49 | } 50 | 51 | File configFile = cmd.hasOption("C") ? new File(cmd.getOptionValue("C")) : new File(System.getProperty("user.home"), ".s-client.properties"); 52 | logger.info("using config file = {}", configFile); 53 | try (ConfigurationService configuration = ConfigurationService.newLoader().loadFrom(configFile)) { 54 | FileUtils.cleanDirectory(configuration.getTemp()); 55 | KeystoreHandler.newLoader().loadAndStore(configuration); 56 | logger.debug("{}", configuration.getStorageInfo().dumpAvailableSpace()); 57 | try (HttpService httpService = new HttpService(configuration)) { 58 | httpService.start(); 59 | if(Desktop.isDesktopSupported()){ 60 | Desktop.getDesktop().browse(URI.create("http://localhost:"+httpService.getPort()+"/web/webclient.html")); 61 | } 62 | httpService.join(); 63 | } 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /a-sync-webclient/src/main/resources/web/lib/images/ui-icons_444444_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davide-imbriaco/a-sync/59f28e55940d868505860c82e4f7776c6c0fb9bd/a-sync-webclient/src/main/resources/web/lib/images/ui-icons_444444_256x240.png -------------------------------------------------------------------------------- /a-sync-webclient/src/main/resources/web/lib/images/ui-icons_555555_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davide-imbriaco/a-sync/59f28e55940d868505860c82e4f7776c6c0fb9bd/a-sync-webclient/src/main/resources/web/lib/images/ui-icons_555555_256x240.png -------------------------------------------------------------------------------- /a-sync-webclient/src/main/resources/web/lib/images/ui-icons_777620_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davide-imbriaco/a-sync/59f28e55940d868505860c82e4f7776c6c0fb9bd/a-sync-webclient/src/main/resources/web/lib/images/ui-icons_777620_256x240.png -------------------------------------------------------------------------------- /a-sync-webclient/src/main/resources/web/lib/images/ui-icons_777777_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davide-imbriaco/a-sync/59f28e55940d868505860c82e4f7776c6c0fb9bd/a-sync-webclient/src/main/resources/web/lib/images/ui-icons_777777_256x240.png -------------------------------------------------------------------------------- /a-sync-webclient/src/main/resources/web/lib/images/ui-icons_cc0000_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davide-imbriaco/a-sync/59f28e55940d868505860c82e4f7776c6c0fb9bd/a-sync-webclient/src/main/resources/web/lib/images/ui-icons_cc0000_256x240.png -------------------------------------------------------------------------------- /a-sync-webclient/src/main/resources/web/lib/images/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davide-imbriaco/a-sync/59f28e55940d868505860c82e4f7776c6c0fb9bd/a-sync-webclient/src/main/resources/web/lib/images/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /a-sync-webclient/src/main/resources/web/webclient.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | a-sync-client 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
loading...
16 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /a-sync-webclient/src/main/resources/web/webclient.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | $('#wc_loading').hide(); 4 | $('#wc_mainContent').show(); 5 | 6 | $('#wc_exit').on('click', function () { 7 | $.get('/api/exit'); 8 | }); 9 | 10 | }); 11 | -------------------------------------------------------------------------------- /a-sync-webclient/src/main/resources/web/webclient.less: -------------------------------------------------------------------------------- 1 | 2 | /* global stuff */ 3 | 4 | html, body{ 5 | margin: 0; 6 | padding: 0; 7 | height: 100%; 8 | } 9 | * { 10 | box-sizing: border-box; 11 | } 12 | -------------------------------------------------------------------------------- /docs/http_relay_protocol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davide-imbriaco/a-sync/59f28e55940d868505860c82e4f7776c6c0fb9bd/docs/http_relay_protocol.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | it.anyplace.sync 5 | a-sync 6 | 1.3 7 | pom 8 | 9 | 10 | a-sync-parent 11 | a-sync-core 12 | a-sync-relay 13 | a-sync-http-relay 14 | a-sync-bep 15 | a-sync-client 16 | a-sync-http-relay-server 17 | a-sync-repository 18 | a-sync-discovery 19 | a-sync-webclient 20 | 21 | 22 | 23 | 24 | 25 | maven-deploy-plugin 26 | 27 | true 28 | 29 | 30 | 31 | 32 | --------------------------------------------------------------------------------