summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/isode')
-rw-r--r--src/com/isode/stroke/filetransfer/FileTransferManagerImpl.java3
-rw-r--r--src/com/isode/stroke/filetransfer/IBBSendSession.java19
-rw-r--r--src/com/isode/stroke/filetransfer/IncomingFileTransferManager.java5
-rw-r--r--src/com/isode/stroke/filetransfer/LocalJingleTransportCandidateGenerator.java9
-rw-r--r--src/com/isode/stroke/filetransfer/OutgoingSIFileTransfer.java149
-rw-r--r--src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxiesManager.java101
-rw-r--r--src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxyFinder.java88
7 files changed, 141 insertions, 233 deletions
diff --git a/src/com/isode/stroke/filetransfer/FileTransferManagerImpl.java b/src/com/isode/stroke/filetransfer/FileTransferManagerImpl.java
index 58ea74e..da5e120 100644
--- a/src/com/isode/stroke/filetransfer/FileTransferManagerImpl.java
+++ b/src/com/isode/stroke/filetransfer/FileTransferManagerImpl.java
@@ -88,8 +88,7 @@ public class FileTransferManagerImpl extends FileTransferManager {
timerFactory,
crypto);
incomingFTManager = new IncomingFileTransferManager(
- jingleSessionManager,
- iqRouter,
+ jingleSessionManager,
transporterFactory,
timerFactory,
crypto);
diff --git a/src/com/isode/stroke/filetransfer/IBBSendSession.java b/src/com/isode/stroke/filetransfer/IBBSendSession.java
index 5a812ff..bb44006 100644
--- a/src/com/isode/stroke/filetransfer/IBBSendSession.java
+++ b/src/com/isode/stroke/filetransfer/IBBSendSession.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010-2013 Isode Limited.
+ * Copyright (c) 2010-2015 Isode Limited.
* All rights reserved.
* See the COPYING file for more information.
*/
@@ -12,6 +12,7 @@
package com.isode.stroke.filetransfer;
import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.signals.SignalConnection;
import com.isode.stroke.signals.Slot2;
import com.isode.stroke.signals.Slot;
import com.isode.stroke.jid.JID;
@@ -31,8 +32,9 @@ public class IBBSendSession {
private int sequenceNumber;
private boolean active;
private boolean waitingForData;
+ private SignalConnection currentRequestOnResponseConnection;
- public IBBSendSession(final String id, final JID from, final JID to, ReadBytestream bytestream, IQRouter router) {
+ public IBBSendSession(final String id, final JID from, final JID to, ReadBytestream bytestream, IQRouter router) {
this.id = id;
this.from = from;
this.to = to;
@@ -52,7 +54,7 @@ public class IBBSendSession {
public void start() {
IBBRequest request = IBBRequest.create(from, to, IBB.createIBBOpen(id, (int)(blockSize)), router);
- request.onResponse.connect(new Slot2<IBB, ErrorPayload>() {
+ currentRequestOnResponseConnection = request.onResponse.connect(new Slot2<IBB, ErrorPayload>() {
@Override
public void call(IBB b, ErrorPayload e) {
handleIBBResponse(b, e);
@@ -66,6 +68,10 @@ public class IBBSendSession {
if (active && router.isAvailable()) {
IBBRequest.create(from, to, IBB.createIBBClose(id), router).send();
}
+ if (currentRequestOnResponseConnection != null) {
+ currentRequestOnResponseConnection.disconnect();
+ currentRequestOnResponseConnection = null;
+ }
finish(null);
}
@@ -83,8 +89,11 @@ public class IBBSendSession {
public final Signal1<FileTransferError> onFinished = new Signal1<FileTransferError>();
public final Signal1<Integer> onBytesSent = new Signal1<Integer>();
-
- private void handleIBBResponse(IBB ibb, ErrorPayload error) {
+ private void handleIBBResponse(IBB ibb, ErrorPayload error) {
+ if (currentRequestOnResponseConnection != null) {
+ currentRequestOnResponseConnection.disconnect();
+ currentRequestOnResponseConnection = null;
+ }
if (error == null && active) {
if (!bytestream.isFinished()) {
sendMoreData();
diff --git a/src/com/isode/stroke/filetransfer/IncomingFileTransferManager.java b/src/com/isode/stroke/filetransfer/IncomingFileTransferManager.java
index ffdf86e..7166d67 100644
--- a/src/com/isode/stroke/filetransfer/IncomingFileTransferManager.java
+++ b/src/com/isode/stroke/filetransfer/IncomingFileTransferManager.java
@@ -30,20 +30,17 @@ import java.util.Vector;
public class IncomingFileTransferManager implements IncomingJingleSessionHandler {
private JingleSessionManager jingleSessionManager;
- private IQRouter router;
private FileTransferTransporterFactory transporterFactory;
private TimerFactory timerFactory;
private CryptoProvider crypto;
private Logger logger_ = Logger.getLogger(this.getClass().getName());
public IncomingFileTransferManager(
- JingleSessionManager jingleSessionManager,
- IQRouter router,
+ JingleSessionManager jingleSessionManager,
FileTransferTransporterFactory transporterFactory,
TimerFactory timerFactory,
CryptoProvider crypto) {
this.jingleSessionManager = jingleSessionManager;
- this.router = router;
this.transporterFactory = transporterFactory;
this.timerFactory = timerFactory;
this.crypto = crypto;
diff --git a/src/com/isode/stroke/filetransfer/LocalJingleTransportCandidateGenerator.java b/src/com/isode/stroke/filetransfer/LocalJingleTransportCandidateGenerator.java
index 3839b26..a4c4623 100644
--- a/src/com/isode/stroke/filetransfer/LocalJingleTransportCandidateGenerator.java
+++ b/src/com/isode/stroke/filetransfer/LocalJingleTransportCandidateGenerator.java
@@ -22,8 +22,11 @@ import com.isode.stroke.signals.SignalConnection;
import com.isode.stroke.signals.Slot1;
import com.isode.stroke.signals.Slot;
import com.isode.stroke.signals.Signal1;
+
+import java.net.Inet6Address;
import java.util.Vector;
import java.util.logging.Logger;
+
import com.isode.stroke.network.HostAddressPort;
import com.isode.stroke.network.HostAddress;
import com.isode.stroke.elements.JingleS5BTransportPayload;
@@ -172,7 +175,11 @@ public class LocalJingleTransportCandidateGenerator {
// get direct candidates
Vector<HostAddressPort> directCandidates = s5bServerManager.getHostAddressPorts();
for(HostAddressPort addressPort : directCandidates) {
- JingleS5BTransportPayload.Candidate candidate = new JingleS5BTransportPayload.Candidate();
+ if (addressPort.getAddress().getInetAddress() instanceof Inet6Address
+ && addressPort.getAddress().getInetAddress().isLinkLocalAddress()) {
+ continue;
+ }
+ JingleS5BTransportPayload.Candidate candidate = new JingleS5BTransportPayload.Candidate();
candidate.type = JingleS5BTransportPayload.Candidate.Type.DirectType;
candidate.jid = ownJID;
candidate.hostPort = addressPort;
diff --git a/src/com/isode/stroke/filetransfer/OutgoingSIFileTransfer.java b/src/com/isode/stroke/filetransfer/OutgoingSIFileTransfer.java
deleted file mode 100644
index 4ba17de..0000000
--- a/src/com/isode/stroke/filetransfer/OutgoingSIFileTransfer.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (c) 2010-2015 Isode Limited.
- * All rights reserved.
- * See the COPYING file for more information.
- */
-/*
- * Copyright (c) 2015 Tarun Gupta.
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-package com.isode.stroke.filetransfer;
-
-import com.isode.stroke.jid.JID;
-import com.isode.stroke.queries.IQRouter;
-import com.isode.stroke.signals.Signal1;
-import com.isode.stroke.elements.ErrorPayload;
-import com.isode.stroke.elements.StreamInitiation;
-import com.isode.stroke.elements.Bytestreams;
-
-public class OutgoingSIFileTransfer implements OutgoingFileTransfer {
-
- private long fileSizeInBytes = 0; //FileTransferVariables
- private String filename = ""; //FileTransferVariables
-
- /**
- * FileTransferMethod.
- */
- @Override
- public String getFileName() {
- return filename;
- }
-
- /**
- * FileTransferMethod.
- */
- @Override
- public long getFileSizeInBytes() {
- return fileSizeInBytes;
- }
-
- /**
- * FileTransferMethod.
- */
- @Override
- public void setFileInfo(final String name, long size) {
- this.filename = name;
- this.fileSizeInBytes = size;
- }
-
- private String id = "";
- private JID from;
- private JID to;
- private String name = "";
- private long size;
- private String description = "";
- private ReadBytestream bytestream;
- private IQRouter iqRouter;
- private SOCKS5BytestreamServer socksServer;
- private IBBSendSession ibbSession;
-
- public OutgoingSIFileTransfer(final String id, final JID from, final JID to, final String name, long size, final String description, ReadBytestream bytestream, IQRouter iqRouter, SOCKS5BytestreamServer socksServer) {
- this.id = id;
- this.from = from;
- this.to = to;
- this.name = name;
- this.size = size;
- this.description = description;
- this.bytestream = bytestream;
- this.iqRouter = iqRouter;
- this.socksServer = socksServer;
- this.ibbSession = ibbSession;
- }
-
- /**
- * OutgoingFileTransferMethod.
- */
- @Override
- public void start() {
- /*
- StreamInitiation::ref streamInitiation(new StreamInitiation());
- streamInitiation.setID(id);
- streamInitiation.setFileInfo(StreamInitiationFileInfo(name, description, size));
- //streamInitiation.addProvidedMethod("http://jabber.org/protocol/bytestreams");
- streamInitiation.addProvidedMethod("http://jabber.org/protocol/ibb");
- StreamInitiationRequest::ref request = StreamInitiationRequest::create(to, streamInitiation, iqRouter);
- request.onResponse.connect(boost::bind(&OutgoingSIFileTransfer::handleStreamInitiationRequestResponse, this, _1, _2));
- request.send();
- */
- }
-
- public void stop() {
- }
-
- public final Signal1<FileTransferError> onFinished = new Signal1<FileTransferError>();
-
- private void handleStreamInitiationRequestResponse(StreamInitiation stream, ErrorPayload error) {
- /*
- if (error) {
- finish(FileTransferError());
- }
- else {
- if (response->getRequestedMethod() == "http://jabber.org/protocol/bytestreams") {
- socksServer->addReadBytestream(id, from, to, bytestream);
- Bytestreams::ref bytestreams(new Bytestreams());
- bytestreams->setStreamID(id);
- HostAddressPort addressPort = socksServer->getAddressPort();
- bytestreams->addStreamHost(Bytestreams::StreamHost(addressPort.getAddress().toString(), from, addressPort.getPort()));
- BytestreamsRequest::ref request = BytestreamsRequest::create(to, bytestreams, iqRouter);
- request->onResponse.connect(boost::bind(&OutgoingSIFileTransfer::handleBytestreamsRequestResponse, this, _1, _2));
- request->send();
- }
- else if (response->getRequestedMethod() == "http://jabber.org/protocol/ibb") {
- ibbSession = boost::make_shared<IBBSendSession>(id, from, to, bytestream, iqRouter);
- ibbSession->onFinished.connect(boost::bind(&OutgoingSIFileTransfer::handleIBBSessionFinished, this, _1));
- ibbSession->start();
- }
- }
- */
- }
-
- private void handleBytestreamsRequestResponse(Bytestreams stream, ErrorPayload error) {
- /*
- if (error) {
- finish(FileTransferError());
- }
- */
- //socksServer->onTransferFinished.connect();
- }
-
- private void finish(FileTransferError error) {
- /*
- if (ibbSession) {
- ibbSession->onFinished.disconnect(boost::bind(&OutgoingSIFileTransfer::handleIBBSessionFinished, this, _1));
- ibbSession.reset();
- }
- socksServer->removeReadBytestream(id, from, to);
- onFinished(error);
- */
- }
-
- private void handleIBBSessionFinished(FileTransferError error) {
- //finish(error);
- }
-
- public void cancel() {
-
- }
-} \ No newline at end of file
diff --git a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxiesManager.java b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxiesManager.java
index eab3031..f541222 100644
--- a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxiesManager.java
+++ b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxiesManager.java
@@ -16,29 +16,29 @@
package com.isode.stroke.filetransfer;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import java.util.logging.Logger;
+
+import com.isode.stroke.elements.S5BProxyRequest;
+import com.isode.stroke.jid.JID;
import com.isode.stroke.network.Connection;
import com.isode.stroke.network.ConnectionFactory;
-import com.isode.stroke.network.DomainNameResolver;
-import com.isode.stroke.network.TimerFactory;
-import com.isode.stroke.network.HostAddressPort;
-import com.isode.stroke.network.DomainNameResolveError;
import com.isode.stroke.network.DomainNameAddressQuery;
+import com.isode.stroke.network.DomainNameResolveError;
+import com.isode.stroke.network.DomainNameResolver;
import com.isode.stroke.network.HostAddress;
+import com.isode.stroke.network.HostAddressPort;
+import com.isode.stroke.network.TimerFactory;
import com.isode.stroke.queries.IQRouter;
-import com.isode.stroke.session.Session;
-import com.isode.stroke.signals.Slot1;
-import com.isode.stroke.signals.Slot2;
import com.isode.stroke.signals.Signal;
import com.isode.stroke.signals.SignalConnection;
-import com.isode.stroke.elements.S5BProxyRequest;
-import com.isode.stroke.jid.JID;
-
-import java.util.Iterator;
-import java.util.Vector;
-import java.util.Collection;
-import java.util.Map;
-import java.util.HashMap;
-import java.util.logging.Logger;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.signals.Slot2;
/**
* - manages list of working S5B proxies
@@ -53,8 +53,6 @@ public class SOCKS5BytestreamProxiesManager {
private JID serviceRoot_;
private Logger logger_ = Logger.getLogger(this.getClass().getName());
- // TODO plonk this into the pair or not
- // TODO think what this is trying to do?
private static class Pair {
public JID jid;
public SOCKS5BytestreamClientSession sock5;
@@ -64,7 +62,8 @@ public class SOCKS5BytestreamProxiesManager {
private Map<String, Collection<Pair> > proxySessions_ = new HashMap<String, Collection<Pair> >();
- /**
+ private SignalConnection onProxiesFoundConnection;
+ /**
* Map between {@link SOCKS5BytestreamClientSession} and a {@link SignalConnection} to their
* {@link SOCKS5BytestreamClientSession#onSessionReady}
*/
@@ -183,31 +182,37 @@ public class SOCKS5BytestreamProxiesManager {
}
public final Signal onDiscoveredProxiesChanged = new Signal();
-
- private void handleProxyFound(final S5BProxyRequest proxy) {
- if (proxy != null) {
- if (new HostAddress(proxy.getStreamHost().host).isValid()) {
- addS5BProxy(proxy);
- onDiscoveredProxiesChanged.emit();
- }
- else {
- DomainNameAddressQuery resolveRequest = resolver_.createAddressQuery(proxy.getStreamHost().host);
- resolveRequest.onResult.connect(new Slot2<Collection<HostAddress>, DomainNameResolveError>() {
- @Override
- public void call(Collection<HostAddress> c, DomainNameResolveError d) {
- handleNameLookupResult(c, d, proxy);
- }
- });
- resolveRequest.run();
- }
- }
- else {
- onDiscoveredProxiesChanged.emit();
- }
- proxyFinder_.stop();
- proxyFinder_ = null;
+
+ private void handleProxiesFound(Collection<? extends S5BProxyRequest> proxyHosts) {
+ if (onProxiesFoundConnection != null) {
+ onProxiesFoundConnection.disconnect();
+ onProxiesFoundConnection = null;
+ }
+ for (final S5BProxyRequest proxy : proxyHosts) {
+ if (proxy != null) {
+ if (new HostAddress(proxy.getStreamHost().host).isValid()) {
+ addS5BProxy(proxy);
+ onDiscoveredProxiesChanged.emit();
+ }
+ else {
+ DomainNameAddressQuery resolveRequest = resolver_.createAddressQuery(proxy.getStreamHost().host);
+ resolveRequest.onResult.connect(new Slot2<Collection<HostAddress>, DomainNameResolveError>() {
+ @Override
+ public void call(Collection<HostAddress> c, DomainNameResolveError d) {
+ handleNameLookupResult(c, d, proxy);
+ }
+ });
+ resolveRequest.run();
+ }
+ }
+ }
+ proxyFinder_.stop();
+ proxyFinder_ = null;
+ if (proxyHosts.isEmpty()) {
+ onDiscoveredProxiesChanged.emit();
+ }
}
-
+
private void handleNameLookupResult(final Collection<HostAddress> addresses, DomainNameResolveError error, S5BProxyRequest proxy) {
if (error != null) {
onDiscoveredProxiesChanged.emit();
@@ -215,7 +220,6 @@ public class SOCKS5BytestreamProxiesManager {
else {
if (addresses.isEmpty()) {
logger_.warning("S5B proxy hostname does not resolve.\n");
- onDiscoveredProxiesChanged.emit();
}
else {
// generate proxy per returned address
@@ -226,18 +230,17 @@ public class SOCKS5BytestreamProxiesManager {
proxyForAddress.setStreamHost(streamHost);
addS5BProxy(proxyForAddress);
}
- onDiscoveredProxiesChanged.emit();
}
+ onDiscoveredProxiesChanged.emit();
}
}
private void queryForProxies() {
proxyFinder_ = new SOCKS5BytestreamProxyFinder(serviceRoot_, iqRouter_);
-
- proxyFinder_.onProxyFound.connect(new Slot1<S5BProxyRequest>() {
+ onProxiesFoundConnection = proxyFinder_.onProxiesFound.connect(new Slot1<List<S5BProxyRequest>>() {
@Override
- public void call(S5BProxyRequest s) {
- handleProxyFound(s);
+ public void call(List<S5BProxyRequest> s) {
+ handleProxiesFound(s);
}
});
proxyFinder_.start();
diff --git a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxyFinder.java b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxyFinder.java
index d856a2f..e880bfb 100644
--- a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxyFinder.java
+++ b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamProxyFinder.java
@@ -16,32 +16,45 @@
package com.isode.stroke.filetransfer;
-import com.isode.stroke.network.HostAddressPort;
-import com.isode.stroke.elements.S5BProxyRequest;
-import com.isode.stroke.elements.ErrorPayload;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import com.isode.stroke.disco.DiscoServiceWalker;
import com.isode.stroke.elements.DiscoInfo;
+import com.isode.stroke.elements.ErrorPayload;
import com.isode.stroke.elements.IQ;
-import com.isode.stroke.disco.DiscoServiceWalker;
-import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.elements.S5BProxyRequest;
+import com.isode.stroke.jid.JID;
import com.isode.stroke.queries.GenericRequest;
+import com.isode.stroke.queries.IQRouter;
import com.isode.stroke.signals.Signal1;
import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot;
import com.isode.stroke.signals.Slot2;
-import com.isode.stroke.jid.JID;
-import java.util.Vector;
-import java.util.logging.Logger;
/*
* This class is designed to find possible SOCKS5 bytestream proxies which are used for peer-to-peer data transfers in
* restrictive environments.
*/
public class SOCKS5BytestreamProxyFinder {
-
+
private JID service;
private IQRouter iqRouter;
private DiscoServiceWalker serviceWalker;
- private Vector<GenericRequest<S5BProxyRequest> > requests = new Vector<GenericRequest<S5BProxyRequest>>();
+ private final List<S5BProxyRequest> proxyHosts = new ArrayList<S5BProxyRequest>();
+ private final Set<GenericRequest<S5BProxyRequest>> pendingRequests = new HashSet<GenericRequest<S5BProxyRequest>>();
+
private SignalConnection onServiceFoundConnection;
+ private SignalConnection onWalkCompleteConnection;
+ private final Map<GenericRequest<S5BProxyRequest>,SignalConnection> requestOnResponseConnections
+ = new HashMap<GenericRequest<S5BProxyRequest>,SignalConnection>();
+
+
private Logger logger_ = Logger.getLogger(this.getClass().getName());
public SOCKS5BytestreamProxyFinder(final JID service, IQRouter iqRouter) {
@@ -57,27 +70,42 @@ public class SOCKS5BytestreamProxyFinder {
handleServiceFound(j, d);
}
});
+ onWalkCompleteConnection = serviceWalker.onWalkComplete.connect(new Slot() {
+
+ @Override
+ public void call() {
+ handleWalkEnded();
+ }
+
+ });
serviceWalker.beginWalk();
}
public void stop() {
- serviceWalker.endWalk();
+ for (SignalConnection onResponseConnection : requestOnResponseConnections.values()) {
+ onResponseConnection.disconnect();
+ }
+ requestOnResponseConnections.clear();
+ serviceWalker.endWalk();
onServiceFoundConnection.disconnect();
+ onWalkCompleteConnection.disconnect();
serviceWalker = null;
}
-
- public final Signal1<S5BProxyRequest> onProxyFound = new Signal1<S5BProxyRequest>();
+
+ public final Signal1<List<S5BProxyRequest>> onProxiesFound = new Signal1<List<S5BProxyRequest>>();
private void sendBytestreamQuery(final JID jid) {
S5BProxyRequest proxyRequest = new S5BProxyRequest();
- GenericRequest<S5BProxyRequest> request = new GenericRequest<S5BProxyRequest>(IQ.Type.Get, jid, proxyRequest, iqRouter);
- request.onResponse.connect(new Slot2<S5BProxyRequest, ErrorPayload>() {
+ final GenericRequest<S5BProxyRequest> requester = new GenericRequest<S5BProxyRequest>(IQ.Type.Get, jid, proxyRequest, iqRouter);
+ SignalConnection requestOnResponseConnection = requester.onResponse.connect(new Slot2<S5BProxyRequest, ErrorPayload>() {
@Override
public void call(S5BProxyRequest s, ErrorPayload e) {
- handleProxyResponse(s, e);
+ handleProxyResponse(requester,s,e);
}
});
- request.send();
+ pendingRequests.add(requester);
+ requestOnResponseConnections.put(requester, requestOnResponseConnection);
+ requester.send();
}
private void handleServiceFound(final JID jid, DiscoInfo discoInfo) {
@@ -85,15 +113,29 @@ public class SOCKS5BytestreamProxyFinder {
sendBytestreamQuery(jid);
}
}
- private void handleProxyResponse(S5BProxyRequest request, ErrorPayload error) {
- if (error != null) {
+
+ private void handleWalkEnded() {
+ if (pendingRequests.isEmpty()) {
+ onProxiesFound.emit(proxyHosts);
+ }
+ }
+
+ private void handleProxyResponse(GenericRequest<S5BProxyRequest> requester,S5BProxyRequest request, ErrorPayload error) {
+ SignalConnection requestOnResponseConnection = requestOnResponseConnections.remove(request);
+ if (requestOnResponseConnection != null) {
+ requestOnResponseConnection.disconnect();
+ }
+ pendingRequests.remove(requester);
+ if (error != null) {
logger_.fine("ERROR\n");
} else {
if (request != null) {
- onProxyFound.emit(request);
- } else {
- //assert(false);
- }
+ logger_.fine("add request\n");
+ proxyHosts.add(request);
+ }
}
+ if (pendingRequests.isEmpty() && !serviceWalker.isActive()) {
+ onProxiesFound.emit(proxyHosts);
+ }
}
} \ No newline at end of file