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