Simplify file API, allow sending hash with file

This commit is contained in:
vanitasvitae 2017-08-20 14:58:46 +02:00
parent 6c51424949
commit 27f6922cf6
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
13 changed files with 199 additions and 294 deletions

View File

@ -45,7 +45,7 @@ import org.jivesoftware.smackx.jingle.component.JingleSession;
import org.jivesoftware.smackx.jingle.element.JingleContentElement;
import org.jivesoftware.smackx.jingle.util.Role;
import org.jivesoftware.smackx.jingle_filetransfer.JingleFileTransferManager;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransferFile;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFile;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleOutgoingFileOffer;
import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileOfferController;
@ -88,10 +88,10 @@ public final class JetManager extends Manager implements JingleDescriptionManage
}
public OutgoingFileOfferController sendEncryptedFile(File file, FullJid recipient, JingleEnvelopeManager envelopeManager) throws Exception {
return sendEncryptedFile(file, null, recipient, envelopeManager);
return sendEncryptedFile(file, JingleFile.fromFile(file, null, null, null), recipient, envelopeManager);
}
public OutgoingFileOfferController sendEncryptedFile(File file, String filename, FullJid recipient, JingleEnvelopeManager envelopeManager) throws Exception {
public OutgoingFileOfferController sendEncryptedFile(File file, JingleFile metadata, FullJid recipient, JingleEnvelopeManager envelopeManager) throws Exception {
if (file == null || !file.exists()) {
throw new IllegalArgumentException("File MUST NOT be null and MUST exist.");
}
@ -103,10 +103,7 @@ public final class JetManager extends Manager implements JingleDescriptionManage
JingleContent content = new JingleContent(JingleContentElement.Creator.initiator, JingleContentElement.Senders.initiator);
session.addContent(content);
JingleOutgoingFileOffer offer = new JingleOutgoingFileOffer(file);
if (filename != null) {
offer.getFile().setName(filename);
}
JingleOutgoingFileOffer offer = new JingleOutgoingFileOffer(file, metadata);
content.setDescription(offer);
JingleTransportManager transportManager = jingleManager.getBestAvailableTransportManager(recipient);
@ -119,7 +116,7 @@ public final class JetManager extends Manager implements JingleDescriptionManage
return offer;
}
public OutgoingFileOfferController sendEncryptedStream(InputStream inputStream, JingleFileTransferFile.StreamFile file, FullJid recipient, JingleEnvelopeManager envelopeManager)
public OutgoingFileOfferController sendEncryptedStream(InputStream inputStream, JingleFile metadata, FullJid recipient, JingleEnvelopeManager envelopeManager)
throws XMPPException.XMPPErrorException, SmackException.FeatureNotSupportedException, SmackException.NotConnectedException,
InterruptedException, SmackException.NoResponseException, NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException,
JingleEnvelopeManager.JingleEncryptionException, NoSuchProviderException, InvalidAlgorithmParameterException {
@ -130,7 +127,7 @@ public final class JetManager extends Manager implements JingleDescriptionManage
JingleContent content = new JingleContent(JingleContentElement.Creator.initiator, JingleContentElement.Senders.initiator);
session.addContent(content);
JingleOutgoingFileOffer offer = new JingleOutgoingFileOffer(file, inputStream);
JingleOutgoingFileOffer offer = new JingleOutgoingFileOffer(inputStream, metadata);
content.setDescription(offer);
JingleTransportManager transportManager = jingleManager.getBestAvailableTransportManager(recipient);

View File

@ -18,7 +18,9 @@ package org.jivesoftware.smackx.jingle_filetransfer;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -41,7 +43,7 @@ import org.jivesoftware.smackx.jingle.element.JingleContentElement;
import org.jivesoftware.smackx.jingle.util.Role;
import org.jivesoftware.smackx.jingle_filetransfer.adapter.JingleFileTransferAdapter;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransfer;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransferFile;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFile;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleIncomingFileOffer;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleIncomingFileRequest;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleOutgoingFileOffer;
@ -95,13 +97,11 @@ public final class JingleFileTransferManager extends Manager implements JingleDe
public OutgoingFileOfferController sendFile(File file, FullJid to)
throws SmackException.NotConnectedException, InterruptedException, XMPPException.XMPPErrorException,
SmackException.NoResponseException, SmackException.FeatureNotSupportedException, FileNotFoundException {
return sendFile(file, null, to);
SmackException.NoResponseException, SmackException.FeatureNotSupportedException, IOException, NoSuchAlgorithmException {
return sendFile(file, JingleFile.fromFile(file, null, null, null), to);
}
public OutgoingFileOfferController sendFile(File file, String alternativeFilename, FullJid to)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, SmackException.FeatureNotSupportedException, FileNotFoundException {
public OutgoingFileOfferController sendFile(File file, JingleFile metadata, FullJid to) throws SmackException.FeatureNotSupportedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, FileNotFoundException {
if (file == null || !file.exists()) {
throw new IllegalArgumentException("File MUST NOT be null and MUST exist.");
}
@ -115,10 +115,7 @@ public final class JingleFileTransferManager extends Manager implements JingleDe
JingleContent content = new JingleContent(JingleContentElement.Creator.initiator, JingleContentElement.Senders.initiator);
session.addContent(content);
JingleOutgoingFileOffer offer = new JingleOutgoingFileOffer(file);
if (alternativeFilename != null) {
offer.getFile().setName(alternativeFilename);
}
JingleOutgoingFileOffer offer = new JingleOutgoingFileOffer(file, metadata);
content.setDescription(offer);
JingleTransportManager transportManager = jingleManager.getBestAvailableTransportManager(to);
@ -130,7 +127,7 @@ public final class JingleFileTransferManager extends Manager implements JingleDe
return offer;
}
public OutgoingFileOfferController sendStream(final InputStream stream, JingleFileTransferFile.StreamFile file, FullJid recipient) throws SmackException.FeatureNotSupportedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
public OutgoingFileOfferController sendStream(final InputStream stream, JingleFile metadata, FullJid recipient) throws SmackException.FeatureNotSupportedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
if (!ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(recipient, getNamespace())) {
throw new SmackException.FeatureNotSupportedException(getNamespace(), recipient);
}
@ -140,7 +137,7 @@ public final class JingleFileTransferManager extends Manager implements JingleDe
JingleContent content = new JingleContent(JingleContentElement.Creator.initiator, JingleContentElement.Senders.initiator);
session.addContent(content);
JingleOutgoingFileOffer outgoingFileOffer = new JingleOutgoingFileOffer(file, stream);
JingleOutgoingFileOffer outgoingFileOffer = new JingleOutgoingFileOffer(stream, metadata);
content.setDescription(outgoingFileOffer);
@ -153,8 +150,8 @@ public final class JingleFileTransferManager extends Manager implements JingleDe
return outgoingFileOffer;
}
public OutgoingFileRequestController requestFile(JingleFileTransferFile.RemoteFile file, FullJid from) {
JingleOutgoingFileRequest request = new JingleOutgoingFileRequest(file);
public OutgoingFileRequestController requestFile(JingleFile metadata, FullJid from) {
JingleOutgoingFileRequest request = new JingleOutgoingFileRequest(metadata);
//TODO at some point.

View File

@ -21,7 +21,7 @@ package org.jivesoftware.smackx.jingle_filetransfer.component;
*/
public abstract class AbstractJingleFileOffer extends JingleFileTransfer {
AbstractJingleFileOffer(JingleFileTransferFile fileTransferFile) {
AbstractJingleFileOffer(JingleFile fileTransferFile) {
super(fileTransferFile);
}
}

View File

@ -19,12 +19,9 @@ package org.jivesoftware.smackx.jingle_filetransfer.component;
/**
* Created by vanitas on 22.07.17.
*/
public abstract class AbstractJingleFileRequest<D extends JingleFileTransferFile> extends JingleFileTransfer {
public abstract class AbstractJingleFileRequest extends JingleFileTransfer {
AbstractJingleFileRequest(D fileTransferFile) {
AbstractJingleFileRequest(JingleFile fileTransferFile) {
super(fileTransferFile);
}
@Override
public abstract D getFile();
}

View File

@ -0,0 +1,154 @@
/**
*
* Copyright 2017 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle_filetransfer.component;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import org.jivesoftware.smackx.hashes.HashManager;
import org.jivesoftware.smackx.hashes.element.HashElement;
import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement;
import com.sun.istack.internal.NotNull;
import com.sun.istack.internal.Nullable;
/**
* Represent a file sent in a file transfer.
* This can be both LocalFile (available to the client), or RemoteFile (file not yet available).
*/
public class JingleFile {
private String name, description, mediaType;
private long size;
private Date date;
private HashElement hashElement;
public static JingleFile fromFile(@NotNull File file, @Nullable String description, @Nullable String mediaType, @Nullable HashManager.ALGORITHM hashAlgorithm) throws NoSuchAlgorithmException, IOException {
HashElement hashElement = null;
if (hashAlgorithm != null) {
hashElement = calculateHash(file, hashAlgorithm);
}
return new JingleFile(file.getName(), description, file.length(), mediaType, new Date(file.lastModified()), hashElement);
}
public JingleFile(String name, String description, long size, String mediaType, Date date, HashElement hashElement) {
this.name = name;
this.description = description;
this.size = size;
this.mediaType = mediaType;
this.date = date;
this.hashElement = hashElement;
}
public JingleFile(JingleFileTransferChildElement element) {
this.name = element.getName();
this.description = element.getDescription();
this.size = element.getSize();
this.mediaType = element.getMediaType();
this.date = element.getDate();
this.hashElement = element.getHash();
}
public static HashElement calculateHash(File file, HashManager.ALGORITHM algorithm) throws NoSuchAlgorithmException, IOException {
if (file == null || !file.exists()) {
throw new IllegalArgumentException("File MUST NOT be null and MUST exist.");
}
MessageDigest digest = HashManager.getMessageDigest(algorithm);
if (digest == null) {
throw new NoSuchAlgorithmException("No algorithm for " + algorithm + " found.");
}
FileInputStream fi = new FileInputStream(file);
DigestInputStream di = new DigestInputStream(fi, digest);
while (di.available() > 0) {
di.read();
}
byte[] d = di.getMessageDigest().digest();
return new HashElement(algorithm, d);
}
public JingleFileTransferChildElement getElement() {
JingleFileTransferChildElement.Builder builder = JingleFileTransferChildElement.getBuilder();
builder.setDate(getDate());
builder.setSize(getSize());
builder.setName(getName());
builder.setDescription(getDescription());
builder.setMediaType(getMediaType());
builder.setHash(getHashElement());
return builder.build();
}
public Date getDate() {
return date;
}
public long getSize() {
return size;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public String getMediaType() {
return mediaType;
}
public HashElement getHashElement() {
return hashElement;
}
public void setName(String name) {
this.name = name;
}
public void setDescription(String description) {
this.description = description;
}
public void setMediaType(String mediaType) {
this.mediaType = mediaType;
}
public void setSize(long size) {
this.size = size;
}
public void setDate(Date date) {
this.date = date;
}
public void setHashElement(HashElement hashElement) {
this.hashElement = hashElement;
}
}

View File

@ -39,12 +39,12 @@ public abstract class JingleFileTransfer extends JingleDescription<JingleFileTra
public static final String NAMESPACE = NAMESPACE_V5;
protected State state;
protected JingleFileTransferFile file;
protected JingleFile metadata;
private final List<ProgressListener> progressListeners = Collections.synchronizedList(new ArrayList<ProgressListener>());
JingleFileTransfer(JingleFileTransferFile file) {
this.file = file;
JingleFileTransfer(JingleFile metadata) {
this.metadata = metadata;
}
public abstract boolean isOffer();
@ -108,11 +108,16 @@ public abstract class JingleFileTransfer extends JingleDescription<JingleFileTra
@Override
public JingleFileTransferElement getElement() {
return new JingleFileTransferElement(file.getElement());
return new JingleFileTransferElement(metadata.getElement());
}
@Override
public State getState() {
return state;
}
@Override
public JingleFile getMetadata() {
return metadata;
}
}

View File

@ -1,225 +0,0 @@
/**
*
* Copyright 2017 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle_filetransfer.component;
import java.io.File;
import java.util.Date;
import org.jivesoftware.smackx.hashes.element.HashElement;
import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement;
/**
* Represent a file sent in a file transfer.
* This can be both LocalFile (available to the client), or RemoteFile (file not yet available).
*/
public abstract class JingleFileTransferFile {
public JingleFileTransferFile() {
}
public JingleFileTransferChildElement getElement() {
JingleFileTransferChildElement.Builder builder = JingleFileTransferChildElement.getBuilder();
builder.setDate(getDate());
builder.setSize(getSize());
builder.setName(getName());
builder.setDescription(getDescription());
builder.setMediaType(getMediaType());
builder.setHash(getHashElement());
return builder.build();
}
public abstract Date getDate();
public abstract long getSize();
public abstract String getName();
public abstract String getDescription();
public abstract String getMediaType();
public abstract HashElement getHashElement();
public static class LocalFile extends JingleFileTransferFile {
private final File file;
private String name;
private String description;
private String mediaType;
private HashElement hashElement;
public LocalFile(File file) {
this(file, null, null);
}
public LocalFile(File file, String description) {
this(file, description, null);
}
public LocalFile(File file, String description, String mediaType) {
super();
this.file = file;
String path = file.getAbsolutePath();
name = path.substring(path.lastIndexOf(File.separator) + 1);
this.description = description;
this.mediaType = mediaType;
}
@Override
public Date getDate() {
return new Date(file.lastModified());
}
public void setDate(Date date) {
}
@Override
public long getSize() {
return file.length();
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String getDescription() {
return description;
}
@Override
public String getMediaType() {
return mediaType;
}
@Override
public HashElement getHashElement() {
return hashElement;
}
public void setDescription(String description) {
this.description = description;
}
public void setMediaType(String mediaType) {
this.mediaType = mediaType;
}
public void setHashElement(HashElement hashElement) {
this.hashElement = hashElement;
}
public File getFile() {
return file;
}
}
public static class RemoteFile extends JingleFileTransferFile {
private final JingleFileTransferChildElement file;
public RemoteFile(JingleFileTransferChildElement file) {
super();
this.file = file;
}
@Override
public String getDescription() {
return file.getDescription();
}
@Override
public String getMediaType() {
return file.getMediaType();
}
@Override
public HashElement getHashElement() {
return file.getHash();
}
@Override
public Date getDate() {
return file.getDate();
}
@Override
public long getSize() {
return file.getSize();
}
@Override
public String getName() {
return file.getName();
}
}
public static class StreamFile extends JingleFileTransferFile {
private String name, description, mediaType;
private long size;
private Date date;
private HashElement hashElement;
public StreamFile(String name, long size, String description, String mediaType, Date date, HashElement hashElement) {
this.name = name;
this.size = size;
this.description = description;
this.mediaType = mediaType;
this.date = date;
this.hashElement = hashElement;
}
@Override
public Date getDate() {
return date;
}
@Override
public long getSize() {
return size;
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public String getMediaType() {
return mediaType;
}
@Override
public HashElement getHashElement() {
return hashElement;
}
}
}

View File

@ -49,7 +49,7 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer implements
private OutputStream target;
public JingleIncomingFileOffer(JingleFileTransferChildElement offer) {
super(new JingleFileTransferFile.RemoteFile(offer));
super(new JingleFile(offer));
this.state = State.pending;
}
@ -66,7 +66,7 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer implements
state = State.active;
HashElement hashElement = file.getHashElement();
HashElement hashElement = metadata.getHashElement();
MessageDigest digest = null;
if (hashElement != null) {
digest = HashManager.getMessageDigest(hashElement.getAlgorithm());
@ -89,8 +89,8 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer implements
while ((length = inputStream.read(bufbuf)) >= 0) {
target.write(bufbuf, 0, length);
read += length;
LOGGER.log(Level.INFO, "Read " + read + " (" + length + ") of " + file.getSize() + " bytes.");
if (read == (int) file.getSize()) {
LOGGER.log(Level.INFO, "Read " + read + " (" + length + ") of " + metadata.getSize() + " bytes.");
if (read == (int) metadata.getSize()) {
break;
}
}
@ -171,9 +171,4 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer implements
session.sendAccept(connection);
}
}
@Override
public JingleFileTransferFile.RemoteFile getFile() {
return (JingleFileTransferFile.RemoteFile) this.file;
}
}

View File

@ -27,10 +27,10 @@ import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferEle
* Created by vanitas on 27.07.17.
* TODO: RemoteFile????
*/
public class JingleIncomingFileRequest extends AbstractJingleFileRequest<JingleFileTransferFile.RemoteFile> implements IncomingFileRequestController {
public class JingleIncomingFileRequest extends AbstractJingleFileRequest implements IncomingFileRequestController {
public JingleIncomingFileRequest(JingleFileTransferChildElement request) {
super(new JingleFileTransferFile.RemoteFile(request));
super(new JingleFile(request));
}
@Override
@ -57,9 +57,4 @@ public class JingleIncomingFileRequest extends AbstractJingleFileRequest<JingleF
public void onBytestreamReady(BytestreamSession bytestreamSession) {
}
@Override
public JingleFileTransferFile.RemoteFile getFile() {
return (JingleFileTransferFile.RemoteFile) file;
}
}

View File

@ -38,13 +38,13 @@ public class JingleOutgoingFileOffer extends AbstractJingleFileOffer implements
private final InputStream source;
public JingleOutgoingFileOffer(File file) throws FileNotFoundException {
super(new JingleFileTransferFile.LocalFile(file));
public JingleOutgoingFileOffer(File file, JingleFile metadata) throws FileNotFoundException {
super(metadata);
this.source = new FileInputStream(file);
}
public JingleOutgoingFileOffer(JingleFileTransferFile.StreamFile streamFile, InputStream inputStream) {
super(streamFile);
public JingleOutgoingFileOffer(InputStream inputStream, JingleFile metadata) {
super(metadata);
this.source = inputStream;
}
@ -100,9 +100,4 @@ public class JingleOutgoingFileOffer extends AbstractJingleFileOffer implements
public boolean isRequest() {
return false;
}
@Override
public JingleFileTransferFile.LocalFile getFile() {
return (JingleFileTransferFile.LocalFile) file;
}
}

View File

@ -24,17 +24,12 @@ import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileReques
/**
* Created by vanitas on 27.07.17.
*/
public class JingleOutgoingFileRequest extends AbstractJingleFileRequest<JingleFileTransferFile.RemoteFile> implements OutgoingFileRequestController {
public class JingleOutgoingFileRequest extends AbstractJingleFileRequest implements OutgoingFileRequestController {
public JingleOutgoingFileRequest(JingleFileTransferFile.RemoteFile file) {
public JingleOutgoingFileRequest(JingleFile file) {
super(file);
}
@Override
public JingleFileTransferFile.RemoteFile getFile() {
return (JingleFileTransferFile.RemoteFile) file;
}
@Override
public JingleElement handleDescriptionInfo(JingleContentDescriptionInfoElement info) {
return null;

View File

@ -19,7 +19,7 @@ package org.jivesoftware.smackx.jingle_filetransfer.controller;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smackx.jingle.JingleDescriptionController;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransferFile;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFile;
import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener;
/**
@ -31,7 +31,7 @@ public interface JingleFileTransferController extends JingleDescriptionControlle
void removeProgressListener(ProgressListener listener);
JingleFileTransferFile getFile();
JingleFile getMetadata();
void cancel(XMPPConnection connection) throws SmackException.NotConnectedException, InterruptedException;
}

View File

@ -33,7 +33,7 @@ import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.transport.jingle_ibb.JingleIBBTransportManager;
import org.jivesoftware.smackx.jingle.transport.jingle_s5b.JingleS5BTransportManager;
import org.jivesoftware.smackx.jingle_filetransfer.JingleFileTransferManager;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransferFile;
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFile;
import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileOfferController;
import org.jivesoftware.smackx.jingle_filetransfer.listener.IncomingFileOfferListener;
import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener;
@ -128,7 +128,7 @@ public class JetIntegrationTest extends AbstractOmemoIntegrationTest {
}
});
ja.sendEncryptedStream(sourceStream, new JingleFileTransferFile.StreamFile("test", sourceBytes.length, "desc", null, null, null), conTwo.getUser().asFullJidOrThrow(), oa);
ja.sendEncryptedStream(sourceStream, new JingleFile("test", "desc", (long) sourceBytes.length, null, null, null), conTwo.getUser().asFullJidOrThrow(), oa);
received.waitForResult(60 * 1000);