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