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