Skip to content
This repository was archived by the owner on Aug 30, 2023. It is now read-only.

Commit 14a5e29

Browse files
authored
feat: rotate cache folder (#494)
1 parent 2f3b563 commit 14a5e29

File tree

4 files changed

+209
-100
lines changed

4 files changed

+209
-100
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package io.sentry.core.cache;
2+
3+
import static io.sentry.core.SentryLevel.ERROR;
4+
5+
import io.sentry.core.ISerializer;
6+
import io.sentry.core.SentryLevel;
7+
import io.sentry.core.SentryOptions;
8+
import io.sentry.core.util.Objects;
9+
import java.io.File;
10+
import java.nio.charset.Charset;
11+
import java.util.Arrays;
12+
import org.jetbrains.annotations.NotNull;
13+
14+
abstract class CacheStrategy {
15+
16+
@SuppressWarnings("CharsetObjectCanBeUsed")
17+
protected static final Charset UTF_8 = Charset.forName("UTF-8");
18+
19+
protected final @NotNull SentryOptions options;
20+
protected final @NotNull ISerializer serializer;
21+
protected final @NotNull File directory;
22+
private final int maxSize;
23+
24+
CacheStrategy(
25+
final @NotNull SentryOptions options,
26+
final @NotNull String directoryPath,
27+
final int maxSize) {
28+
Objects.requireNonNull(directoryPath, "Directory is required.");
29+
this.options = Objects.requireNonNull(options, "SentryOptions is required.");
30+
31+
this.serializer = options.getSerializer();
32+
this.directory = new File(directoryPath);
33+
34+
this.maxSize = maxSize;
35+
}
36+
37+
/**
38+
* Check if a dir. is valid and have write and read permission
39+
*
40+
* @return true if valid and has permissions or false otherwise
41+
*/
42+
protected boolean isDirectoryValid() {
43+
if (!directory.isDirectory() || !directory.canWrite() || !directory.canRead()) {
44+
options
45+
.getLogger()
46+
.log(
47+
ERROR,
48+
"The directory for caching files is inaccessible.: %s",
49+
directory.getAbsolutePath());
50+
return false;
51+
}
52+
return true;
53+
}
54+
55+
/**
56+
* Sort files from oldest to the newest using the lastModified method
57+
*
58+
* @param files the Files
59+
*/
60+
private void sortFilesOldestToNewest(@NotNull File[] files) {
61+
// just sort it if more than 1 file
62+
if (files.length > 1) {
63+
Arrays.sort(files, (f1, f2) -> Long.compare(f1.lastModified(), f2.lastModified()));
64+
}
65+
}
66+
67+
/**
68+
* Rotates the caching folder if full, deleting the oldest files first
69+
*
70+
* @param files the Files
71+
*/
72+
protected void rotateCacheIfNeeded(final @NotNull File[] files) {
73+
final int length = files.length;
74+
if (length >= maxSize) {
75+
options
76+
.getLogger()
77+
.log(SentryLevel.WARNING, "Cache folder if full (respecting maxSize). Rotating files");
78+
final int totalToBeDeleted = (length - maxSize) + 1;
79+
80+
sortFilesOldestToNewest(files);
81+
82+
// delete files from the top of the Array as its sorted by the oldest to the newest
83+
for (int i = 0; i < totalToBeDeleted; i++) {
84+
final File file = files[i];
85+
// sanity check if the file actually exists.
86+
if (!file.delete()) {
87+
options
88+
.getLogger()
89+
.log(SentryLevel.WARNING, "File can't be deleted: %s", file.getAbsolutePath());
90+
}
91+
}
92+
}
93+
}
94+
}

sentry-core/src/main/java/io/sentry/core/cache/DiskCache.java

Lines changed: 17 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@
55
import static io.sentry.core.SentryLevel.WARNING;
66
import static java.lang.String.format;
77

8-
import io.sentry.core.ISerializer;
98
import io.sentry.core.SentryEvent;
10-
import io.sentry.core.SentryLevel;
119
import io.sentry.core.SentryOptions;
12-
import io.sentry.core.util.Objects;
1310
import java.io.BufferedReader;
1411
import java.io.BufferedWriter;
1512
import java.io.File;
@@ -22,7 +19,6 @@
2219
import java.io.OutputStreamWriter;
2320
import java.io.Reader;
2421
import java.io.Writer;
25-
import java.nio.charset.Charset;
2622
import java.util.ArrayList;
2723
import java.util.Iterator;
2824
import java.util.List;
@@ -34,39 +30,19 @@
3430
* configured directory.
3531
*/
3632
@ApiStatus.Internal
37-
public final class DiskCache implements IEventCache {
33+
public final class DiskCache extends CacheStrategy implements IEventCache {
3834
/** File suffix added to all serialized event files. */
3935
public static final String FILE_SUFFIX = ".sentry-event";
4036

41-
@SuppressWarnings("CharsetObjectCanBeUsed")
42-
private static final Charset UTF_8 = Charset.forName("UTF-8");
43-
44-
private final File directory;
45-
private final int maxSize;
46-
private final ISerializer serializer;
47-
private final SentryOptions options;
48-
49-
public DiskCache(SentryOptions options) {
50-
Objects.requireNonNull(options.getCacheDirPath(), "Cache dir. path is required.");
51-
this.directory = new File(options.getCacheDirPath());
52-
this.maxSize = options.getCacheDirSize();
53-
this.serializer = options.getSerializer();
54-
this.options = options;
37+
public DiskCache(final @NotNull SentryOptions options) {
38+
super(options, options.getCacheDirPath(), options.getCacheDirSize());
5539
}
5640

5741
@Override
58-
public void store(SentryEvent event) {
59-
if (getNumberOfStoredEvents() >= maxSize) {
60-
options
61-
.getLogger()
62-
.log(
63-
SentryLevel.WARNING,
64-
"Disk cache full (respecting maxSize). Not storing event {}",
65-
event);
66-
return;
67-
}
42+
public void store(final @NotNull SentryEvent event) {
43+
rotateCacheIfNeeded(allEventFiles());
6844

69-
File eventFile = getEventFile(event);
45+
final File eventFile = getEventFile(event);
7046
if (eventFile.exists()) {
7147
options
7248
.getLogger()
@@ -92,8 +68,8 @@ public void store(SentryEvent event) {
9268
}
9369

9470
@Override
95-
public void discard(SentryEvent event) {
96-
File eventFile = getEventFile(event);
71+
public void discard(final @NotNull SentryEvent event) {
72+
final File eventFile = getEventFile(event);
9773
if (eventFile.exists()) {
9874
options
9975
.getLogger()
@@ -107,34 +83,17 @@ public void discard(SentryEvent event) {
10783
}
10884
}
10985

110-
private int getNumberOfStoredEvents() {
111-
return allEventFiles().length;
112-
}
113-
114-
private boolean isDirectoryValid() {
115-
if (!directory.isDirectory() || !directory.canWrite() || !directory.canRead()) {
116-
options
117-
.getLogger()
118-
.log(
119-
ERROR,
120-
"The directory for caching Sentry events is inaccessible.: %s",
121-
directory.getAbsolutePath());
122-
return false;
123-
}
124-
return true;
125-
}
126-
127-
private File getEventFile(SentryEvent event) {
86+
private @NotNull File getEventFile(final @NotNull SentryEvent event) {
12887
return new File(directory.getAbsolutePath(), event.getEventId().toString() + FILE_SUFFIX);
12988
}
13089

13190
@Override
13291
public @NotNull Iterator<SentryEvent> iterator() {
133-
File[] allCachedEvents = allEventFiles();
92+
final File[] allCachedEvents = allEventFiles();
13493

135-
List<SentryEvent> ret = new ArrayList<>(allCachedEvents.length);
94+
final List<SentryEvent> ret = new ArrayList<>(allCachedEvents.length);
13695

137-
for (File f : allCachedEvents) {
96+
for (final File f : allCachedEvents) {
13897
try (final Reader reader =
13998
new BufferedReader(new InputStreamReader(new FileInputStream(f), UTF_8))) {
14099

@@ -159,10 +118,12 @@ private File getEventFile(SentryEvent event) {
159118
return ret.iterator();
160119
}
161120

162-
private File[] allEventFiles() {
121+
private @NotNull File[] allEventFiles() {
163122
if (isDirectoryValid()) {
164-
// TODO: we need to order by oldest to the newest here
165-
return directory.listFiles((__, fileName) -> fileName.endsWith(FILE_SUFFIX));
123+
final File[] files = directory.listFiles((__, fileName) -> fileName.endsWith(FILE_SUFFIX));
124+
if (files != null) {
125+
return files;
126+
}
166127
}
167128
return new File[] {};
168129
}

sentry-core/src/main/java/io/sentry/core/cache/SessionCache.java

Lines changed: 9 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import static java.lang.String.format;
88

99
import io.sentry.core.DateUtils;
10-
import io.sentry.core.ISerializer;
1110
import io.sentry.core.SentryEnvelope;
1211
import io.sentry.core.SentryEnvelopeItem;
1312
import io.sentry.core.SentryItemType;
@@ -33,7 +32,6 @@
3332
import java.io.OutputStreamWriter;
3433
import java.io.Reader;
3534
import java.io.Writer;
36-
import java.nio.charset.Charset;
3735
import java.util.ArrayList;
3836
import java.util.Date;
3937
import java.util.Iterator;
@@ -46,7 +44,7 @@
4644
import org.jetbrains.annotations.Nullable;
4745

4846
@ApiStatus.Internal
49-
public final class SessionCache implements IEnvelopeCache {
47+
public final class SessionCache extends CacheStrategy implements IEnvelopeCache {
5048

5149
/** File suffix added to all serialized envelopes files. */
5250
static final String SUFFIX_ENVELOPE_FILE = ".envelope";
@@ -55,37 +53,17 @@ public final class SessionCache implements IEnvelopeCache {
5553
static final String SUFFIX_CURRENT_SESSION_FILE = ".json";
5654
static final String CRASH_MARKER_FILE = ".sentry-native/last_crash";
5755

58-
@SuppressWarnings("CharsetObjectCanBeUsed")
59-
private static final Charset UTF_8 = Charset.forName("UTF-8");
60-
61-
private final @NotNull File directory;
62-
private final int maxSize;
63-
private final @NotNull ISerializer serializer;
64-
private final @NotNull SentryOptions options;
65-
6656
private final @NotNull Map<SentryEnvelope, String> fileNameMap = new WeakHashMap<>();
6757

6858
public SessionCache(final @NotNull SentryOptions options) {
69-
Objects.requireNonNull(options.getSessionsPath(), "sessions dir. path is required.");
70-
this.directory = new File(options.getSessionsPath());
71-
this.maxSize = options.getSessionsDirSize();
72-
this.serializer = options.getSerializer();
73-
this.options = options;
59+
super(options, options.getSessionsPath(), options.getSessionsDirSize());
7460
}
7561

7662
@Override
7763
public void store(final @NotNull SentryEnvelope envelope, final @Nullable Object hint) {
7864
Objects.requireNonNull(envelope, "Envelope is required.");
7965

80-
if (getNumberOfStoredEnvelopes() >= maxSize) {
81-
options
82-
.getLogger()
83-
.log(
84-
SentryLevel.WARNING,
85-
"Disk cache full (respecting maxSize). Not storing envelope {}",
86-
envelope);
87-
return;
88-
}
66+
rotateCacheIfNeeded(allEnvelopeFiles());
8967

9068
final File currentSessionFile = getCurrentSessionFile();
9169

@@ -185,7 +163,7 @@ public void store(final @NotNull SentryEnvelope envelope, final @Nullable Object
185163
* @param markerFile the marker file
186164
* @return the timestamp as Date
187165
*/
188-
private Date getTimestampFromCrashMarkerFile(final @NotNull File markerFile) {
166+
private @Nullable Date getTimestampFromCrashMarkerFile(final @NotNull File markerFile) {
189167
try (final BufferedReader reader =
190168
new BufferedReader(new InputStreamReader(new FileInputStream(markerFile), UTF_8))) {
191169
final String timestamp = reader.readLine();
@@ -299,23 +277,6 @@ public void discard(final @NotNull SentryEnvelope envelope) {
299277
}
300278
}
301279

302-
private int getNumberOfStoredEnvelopes() {
303-
return allEnvelopeFiles().length;
304-
}
305-
306-
private boolean isDirectoryValid() {
307-
if (!directory.isDirectory() || !directory.canWrite() || !directory.canRead()) {
308-
options
309-
.getLogger()
310-
.log(
311-
ERROR,
312-
"The directory for caching Sentry envelopes is inaccessible.: %s",
313-
directory.getAbsolutePath());
314-
return false;
315-
}
316-
return true;
317-
}
318-
319280
/**
320281
* Returns the envelope's file path. If the envelope has no eventId header, it generates a random
321282
* file name to it.
@@ -378,7 +339,11 @@ private boolean isDirectoryValid() {
378339
private @NotNull File[] allEnvelopeFiles() {
379340
if (isDirectoryValid()) {
380341
// lets filter the session.json here
381-
return directory.listFiles((__, fileName) -> fileName.endsWith(SUFFIX_ENVELOPE_FILE));
342+
final File[] files =
343+
directory.listFiles((__, fileName) -> fileName.endsWith(SUFFIX_ENVELOPE_FILE));
344+
if (files != null) {
345+
return files;
346+
}
382347
}
383348
return new File[] {};
384349
}

0 commit comments

Comments
 (0)