1 package org
.asamk
.signal
.manager
.storage
.messageCache
;
3 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
4 import org
.asamk
.signal
.manager
.util
.IOUtils
;
5 import org
.asamk
.signal
.manager
.util
.MessageCacheUtils
;
6 import org
.slf4j
.Logger
;
7 import org
.slf4j
.LoggerFactory
;
8 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
11 import java
.io
.IOException
;
12 import java
.nio
.file
.Files
;
13 import java
.util
.Arrays
;
14 import java
.util
.Collections
;
15 import java
.util
.Objects
;
16 import java
.util
.stream
.Stream
;
18 public class MessageCache
{
20 private static final Logger logger
= LoggerFactory
.getLogger(MessageCache
.class);
22 private final File messageCachePath
;
24 public MessageCache(final File messageCachePath
) {
25 this.messageCachePath
= messageCachePath
;
28 public Iterable
<CachedMessage
> getCachedMessages() {
29 if (!messageCachePath
.exists()) {
30 return Collections
.emptyList();
33 return Arrays
.stream(Objects
.requireNonNull(messageCachePath
.listFiles())).flatMap(dir
-> {
35 return Stream
.of(dir
);
38 final var files
= Objects
.requireNonNull(dir
.listFiles());
39 if (files
.length
== 0) {
41 Files
.delete(dir
.toPath());
42 } catch (IOException e
) {
43 logger
.warn("Failed to delete cache dir “{}”, ignoring: {}", dir
, e
.getMessage());
45 return Stream
.empty();
47 return Arrays
.stream(files
).filter(File
::isFile
);
48 }).map(CachedMessage
::new).toList();
51 public CachedMessage
cacheMessage(SignalServiceEnvelope envelope
, RecipientId recipientId
) {
52 final var now
= System
.currentTimeMillis();
56 cacheFile
= getMessageCacheFile(recipientId
, now
, envelope
.getTimestamp());
57 } catch (IOException e
) {
58 logger
.warn("Failed to create recipient folder in disk cache: {}", e
.getMessage());
59 throw new RuntimeException(e
);
62 final var cachedMessage
= new CachedMessage(cacheFile
, envelope
);
64 MessageCacheUtils
.storeEnvelope(envelope
, cacheFile
);
66 } catch (IOException e
) {
67 logger
.warn("Failed to store encrypted message in disk cache, ignoring: {}", e
.getMessage());
72 public CachedMessage
replaceSender(CachedMessage cachedMessage
, RecipientId sender
) throws IOException
{
73 final var cacheFile
= getMessageCacheFile(sender
, cachedMessage
.getFile().getName());
74 if (cacheFile
.equals(cachedMessage
.getFile())) {
77 logger
.debug("Moving cached message {} to {}", cachedMessage
.getFile().toPath(), cacheFile
.toPath());
78 Files
.move(cachedMessage
.getFile().toPath(), cacheFile
.toPath());
79 return new CachedMessage(cacheFile
);
82 public void deleteMessages(final RecipientId recipientId
) {
83 final var recipientMessageCachePath
= getMessageCachePath(recipientId
);
84 if (!recipientMessageCachePath
.exists()) {
88 for (var file
: Objects
.requireNonNull(recipientMessageCachePath
.listFiles())) {
94 Files
.delete(file
.toPath());
95 } catch (IOException e
) {
96 logger
.warn("Failed to delete cache file “{}”, ignoring: {}", file
, e
.getMessage());
101 private File
getMessageCachePath(RecipientId recipientId
) {
102 if (recipientId
== null) {
103 return messageCachePath
;
106 var sender
= String
.valueOf(recipientId
.id());
107 return new File(messageCachePath
, sender
.replace("/", "_"));
110 private File
getMessageCacheFile(RecipientId recipientId
, String filename
) throws IOException
{
111 var cachePath
= getMessageCachePath(recipientId
);
112 IOUtils
.createPrivateDirectories(cachePath
);
113 return new File(cachePath
, filename
);
116 private File
getMessageCacheFile(RecipientId recipientId
, long now
, long timestamp
) throws IOException
{
117 var cachePath
= getMessageCachePath(recipientId
);
118 IOUtils
.createPrivateDirectories(cachePath
);
119 return new File(cachePath
, now
+ "_" + timestamp
);
122 public void mergeRecipients(final RecipientId recipientId
, final RecipientId toBeMergedRecipientId
) {
123 final var toBeMergedMessageCachePath
= getMessageCachePath(toBeMergedRecipientId
);
124 if (!toBeMergedMessageCachePath
.exists()) {
128 for (var file
: Objects
.requireNonNull(toBeMergedMessageCachePath
.listFiles())) {
129 if (!file
.isFile()) {
134 final var cacheFile
= getMessageCacheFile(recipientId
, file
.getName());
135 Files
.move(file
.toPath(), cacheFile
.toPath());
136 } catch (IOException e
) {
137 logger
.warn("Failed to move cache file “{}”, ignoring: {}", file
, e
.getMessage(), e
);