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