]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java
Add command to get an attachment (#1080)
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / AttachmentHelper.java
1 package org.asamk.signal.manager.helper;
2
3 import org.asamk.signal.manager.AttachmentStore;
4 import org.asamk.signal.manager.SignalDependencies;
5 import org.asamk.signal.manager.api.AttachmentInvalidException;
6 import org.asamk.signal.manager.config.ServiceConfig;
7 import org.asamk.signal.manager.util.AttachmentUtils;
8 import org.asamk.signal.manager.util.IOUtils;
9 import org.signal.libsignal.protocol.InvalidMessageException;
10 import org.slf4j.Logger;
11 import org.slf4j.LoggerFactory;
12 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
13 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
14 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
15 import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
16 import org.whispersystems.signalservice.api.util.StreamDetails;
17
18 import java.io.File;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.OutputStream;
22 import java.nio.file.Files;
23 import java.util.ArrayList;
24 import java.util.List;
25
26 public class AttachmentHelper {
27
28 private final static Logger logger = LoggerFactory.getLogger(AttachmentHelper.class);
29
30 private final SignalDependencies dependencies;
31 private final AttachmentStore attachmentStore;
32
33 public AttachmentHelper(final Context context) {
34 this.dependencies = context.getDependencies();
35 this.attachmentStore = context.getAttachmentStore();
36 }
37
38 public File getAttachmentFile(SignalServiceAttachmentPointer pointer) {
39 return attachmentStore.getAttachmentFile(pointer);
40 }
41
42 public StreamDetails retrieveAttachment(final String id) throws IOException {
43 return attachmentStore.retrieveAttachment(id);
44 }
45
46
47 public List<SignalServiceAttachment> uploadAttachments(final List<String> attachments) throws AttachmentInvalidException, IOException {
48 var attachmentStreams = AttachmentUtils.createAttachmentStreams(attachments);
49
50 // Upload attachments here, so we only upload once even for multiple recipients
51 var attachmentPointers = new ArrayList<SignalServiceAttachment>(attachmentStreams.size());
52 for (var attachmentStream : attachmentStreams) {
53 attachmentPointers.add(uploadAttachment(attachmentStream));
54 }
55 return attachmentPointers;
56 }
57
58 public SignalServiceAttachmentPointer uploadAttachment(String attachment) throws IOException, AttachmentInvalidException {
59 var attachmentStream = AttachmentUtils.createAttachmentStream(attachment);
60 return uploadAttachment(attachmentStream);
61 }
62
63 public SignalServiceAttachmentPointer uploadAttachment(SignalServiceAttachmentStream attachment) throws IOException {
64 var messageSender = dependencies.getMessageSender();
65 return messageSender.uploadAttachment(attachment);
66 }
67
68 public void downloadAttachment(final SignalServiceAttachment attachment) {
69 if (!attachment.isPointer()) {
70 logger.warn("Invalid state, can't store an attachment stream.");
71 }
72
73 var pointer = attachment.asPointer();
74 if (pointer.getPreview().isPresent()) {
75 final var preview = pointer.getPreview().get();
76 try {
77 attachmentStore.storeAttachmentPreview(pointer,
78 outputStream -> outputStream.write(preview, 0, preview.length));
79 } catch (IOException e) {
80 logger.warn("Failed to download attachment preview, ignoring: {}", e.getMessage());
81 }
82 }
83
84 try {
85 attachmentStore.storeAttachment(pointer, outputStream -> this.retrieveAttachment(pointer, outputStream));
86 } catch (IOException e) {
87 logger.warn("Failed to download attachment ({}), ignoring: {}", pointer.getRemoteId(), e.getMessage());
88 }
89 }
90
91 void retrieveAttachment(SignalServiceAttachment attachment, OutputStream outputStream) throws IOException {
92 retrieveAttachment(attachment, input -> IOUtils.copyStream(input, outputStream));
93 }
94
95 public void retrieveAttachment(
96 SignalServiceAttachment attachment, AttachmentHandler consumer
97 ) throws IOException {
98 if (attachment.isStream()) {
99 var input = attachment.asStream().getInputStream();
100 // don't close input stream here, it might be reused later (e.g. with contact sync messages ...)
101 consumer.handle(input);
102 return;
103 }
104
105 final var pointer = attachment.asPointer();
106 logger.debug("Retrieving attachment {} with size {}", pointer.getRemoteId(), pointer.getSize());
107 var tmpFile = IOUtils.createTempFile();
108 try (var input = retrieveAttachmentAsStream(pointer, tmpFile)) {
109 consumer.handle(input);
110 } finally {
111 try {
112 Files.delete(tmpFile.toPath());
113 } catch (IOException e) {
114 logger.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
115 tmpFile,
116 e.getMessage());
117 }
118 }
119 }
120
121 private InputStream retrieveAttachmentAsStream(
122 SignalServiceAttachmentPointer pointer, File tmpFile
123 ) throws IOException {
124 try {
125 return dependencies.getMessageReceiver()
126 .retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE);
127 } catch (MissingConfigurationException | InvalidMessageException e) {
128 throw new IOException(e);
129 }
130 }
131
132 @FunctionalInterface
133 public interface AttachmentHandler {
134
135 void handle(InputStream inputStream) throws IOException;
136 }
137 }